dsh-mobile 0.8.6 → 0.8.8
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 +27 -2
- package/CONTRIBUTORS.md +12 -0
- package/README.en.md +20 -14
- package/README.md +17 -13
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/apps/mobile/README.md +21 -7
- package/apps/mobile/README.zh-CN.md +21 -7
- package/apps/mobile/release.json +3 -3
- package/assets/brand/plugin-icon.png +0 -0
- package/docs/ATTACH_EXISTING_FRPS.en.md +2 -2
- package/docs/CPOLAR_SUBDOMAIN.en.md +1 -1
- package/docs/CPOLAR_SUBDOMAIN.md +1 -1
- package/docs/IROH.en.md +5 -5
- package/docs/IROH.md +6 -6
- package/docs/MOBILE_COMPOSER_TOOLBAR.en.md +2 -2
- package/docs/MOBILE_COMPOSER_TOOLBAR.md +2 -2
- package/docs/MOBILE_PLUGIN_ADAPTATION.en.md +9 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.md +9 -0
- package/docs/MOBILE_PLUGIN_BASELINE.en.md +1 -1
- package/docs/MOBILE_PLUGIN_BASELINE.md +1 -1
- package/docs/WEB_PUSH.en.md +2 -2
- package/docs/WEB_PUSH.md +2 -2
- package/index.mjs +6 -0
- package/lib/client.js +359 -12
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +25 -3
- package/lib/index.mjs +319 -107
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +489 -3
- package/lib/mobile-layout.js.map +1 -1
- package/native/iroh-host/README.md +10 -4
- package/package.json +9 -4
- package/packages/question-fixes/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -3813,8 +3813,18 @@ window.__ModuleLoader__.load({
|
|
|
3813
3813
|
return;
|
|
3814
3814
|
}
|
|
3815
3815
|
}
|
|
3816
|
-
/**
|
|
3817
|
-
function
|
|
3816
|
+
/** Resolve the opaque Session id carried by the App's internal notification route. */
|
|
3817
|
+
function sessionIdFromNotificationRoute(route, base = "https://mobile.invalid/") {
|
|
3818
|
+
if (route === null || route === void 0) return void 0;
|
|
3819
|
+
let encoded;
|
|
3820
|
+
try {
|
|
3821
|
+
encoded = new URL(route, base).searchParams.get("dsh-session");
|
|
3822
|
+
} catch {
|
|
3823
|
+
return;
|
|
3824
|
+
}
|
|
3825
|
+
return decodeTaskNotificationNavigate(encoded);
|
|
3826
|
+
}
|
|
3827
|
+
/** Parse a `task-notify` SSE payload; rejects anything malformed or empty. */ function parseTaskNotifyPayload(value) {
|
|
3818
3828
|
let record;
|
|
3819
3829
|
if (typeof value === "string") try {
|
|
3820
3830
|
const parsed = JSON.parse(value);
|
|
@@ -3854,22 +3864,46 @@ window.__ModuleLoader__.load({
|
|
|
3854
3864
|
return true;
|
|
3855
3865
|
}
|
|
3856
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
|
+
/**
|
|
3857
3882
|
* Deliver one notification through the native bridge when available.
|
|
3858
3883
|
* Returns false when there is no bridge (desktop browsers keep their own
|
|
3859
3884
|
* UI) so callers can fall back; a denial flips the module sticky flag via
|
|
3860
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.
|
|
3861
3893
|
*/
|
|
3862
3894
|
function fireTaskNotifyEvent(event, onDenied) {
|
|
3863
3895
|
const bridge = readNativeBridge();
|
|
3864
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
|
+
});
|
|
3865
3903
|
Promise.resolve().then(() => bridge.capabilities()).then((capabilities) => {
|
|
3866
3904
|
if (!capabilities.includes("notification.notify")) return;
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
body: event.body,
|
|
3870
|
-
tag: event.tag,
|
|
3871
|
-
...event.navigate === void 0 ? {} : { navigate: event.navigate }
|
|
3872
|
-
});
|
|
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());
|
|
3873
3907
|
}).then(() => void 0, (error) => {
|
|
3874
3908
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
3875
3909
|
if ((code === "permission_denied" || code === "denied") && onDenied !== void 0) onDenied();
|
|
@@ -4306,6 +4340,13 @@ window.__ModuleLoader__.load({
|
|
|
4306
4340
|
.dsh-mobile-settings_selector:hover:not(:disabled) { background:var(--dsw-alias-interactive-bg-hover); }
|
|
4307
4341
|
.dsh-mobile-settings_selector:active:not(:disabled) { background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover)); }
|
|
4308
4342
|
.dsh-mobile-settings_selector:focus-visible { outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb); outline-offset:2px; }
|
|
4343
|
+
/* Row switches (session prefetch, composer collapse) pair a visually-hidden
|
|
4344
|
+
native checkbox with an On/Off pill. Hide the checkbox so the pill is the only
|
|
4345
|
+
visible control, while the label keeps the 48px row a tap target and the input
|
|
4346
|
+
stays keyboard-focusable; the pill's :focus-visible ring is driven by the
|
|
4347
|
+
checkbox's focus via :has. */
|
|
4348
|
+
.dsh-mobile-settings_switchInput { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; opacity:0; }
|
|
4349
|
+
.dsh-mobile-settings_row:has(.dsh-mobile-settings_switchInput:focus-visible) .dsh-mobile-settings_selector { outline:2px solid var(--dsw-alias-label-primary-bluish,#2563eb); outline-offset:2px; }
|
|
4309
4350
|
.dsh-mobile-settings_selector:disabled { cursor:wait; opacity:.55; }
|
|
4310
4351
|
.dsh-mobile-settings_fontControls { display:flex; align-items:center; gap:8px; flex-shrink:0; }
|
|
4311
4352
|
.dsh-mobile-settings_fontControls button { min-width:48px; padding:0 12px; }
|
|
@@ -7098,6 +7139,124 @@ window.__ModuleLoader__.load({
|
|
|
7098
7139
|
}
|
|
7099
7140
|
}, copy.reset)));
|
|
7100
7141
|
}
|
|
7142
|
+
// src/composer-collapse.ts
|
|
7143
|
+
/**
|
|
7144
|
+
* Storage key for the opt-in switch.
|
|
7145
|
+
*
|
|
7146
|
+
* The setting row lives in the client bundle while the tracker lives in the
|
|
7147
|
+
* layout bundle, and tsdown.config.ts emits those as two independent CJS modules
|
|
7148
|
+
* that each inline this file. A module-level singleton therefore cannot carry the
|
|
7149
|
+
* value across: the row would flip its own copy while the layout kept the
|
|
7150
|
+
* boot-time value, and the toggle would only apply after a reload. The persisted
|
|
7151
|
+
* flag plus the two events below are the shared state instead, mirroring the
|
|
7152
|
+
* session-prefetch opt-in.
|
|
7153
|
+
*/
|
|
7154
|
+
const COMPOSER_COLLAPSE_STORAGE_KEY = "dsh-mobile.composer-collapse.enabled";
|
|
7155
|
+
const COMPOSER_COLLAPSE_EVENT = "dsh-mobile:composer-collapse-change";
|
|
7156
|
+
/**
|
|
7157
|
+
* Last value written in this page when storage refused the write.
|
|
7158
|
+
*
|
|
7159
|
+
* This is a per-bundle fallback for the blocked-storage case only: it is not the
|
|
7160
|
+
* cross-bundle transport (that is the persisted flag plus the events above). It
|
|
7161
|
+
* exists so a switch that cannot be persisted still reads back as flipped in the
|
|
7162
|
+
* same page, instead of the row snapping back while the layout obeys the event.
|
|
7163
|
+
*/
|
|
7164
|
+
let unpersistedEnabled;
|
|
7165
|
+
/** Read the persisted opt-in. Defaults to off; unreadable storage keeps it off. */
|
|
7166
|
+
function readComposerCollapseEnabled(storage) {
|
|
7167
|
+
try {
|
|
7168
|
+
const stored = (storage === void 0 ? globalThis.localStorage : storage)?.getItem("dsh-mobile.composer-collapse.enabled") ?? null;
|
|
7169
|
+
if (stored !== null) return stored === "true";
|
|
7170
|
+
return storage === void 0 ? unpersistedEnabled ?? false : false;
|
|
7171
|
+
} catch {
|
|
7172
|
+
return storage === void 0 ? unpersistedEnabled ?? false : false;
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
/** Persist the opt-in and notify the layout bundle in the same page. */
|
|
7176
|
+
function writeComposerCollapseEnabled(enabled, storage) {
|
|
7177
|
+
try {
|
|
7178
|
+
const resolved = storage === void 0 ? globalThis.localStorage : storage;
|
|
7179
|
+
if (resolved === null) throw new Error("storage unavailable");
|
|
7180
|
+
resolved.setItem(COMPOSER_COLLAPSE_STORAGE_KEY, String(enabled));
|
|
7181
|
+
if (storage === void 0) unpersistedEnabled = void 0;
|
|
7182
|
+
} catch {
|
|
7183
|
+
if (storage === void 0) unpersistedEnabled = enabled;
|
|
7184
|
+
}
|
|
7185
|
+
try {
|
|
7186
|
+
globalThis.dispatchEvent?.(new CustomEvent(COMPOSER_COLLAPSE_EVENT, { detail: { enabled } }));
|
|
7187
|
+
} catch {}
|
|
7188
|
+
}
|
|
7189
|
+
/**
|
|
7190
|
+
* Subscribe to opt-in changes coming from either bundle. The listener is not
|
|
7191
|
+
* called with the initial value; read it once with readComposerCollapseEnabled().
|
|
7192
|
+
*/
|
|
7193
|
+
function subscribeComposerCollapseEnabled(listener) {
|
|
7194
|
+
const onStorage = (event) => {
|
|
7195
|
+
if (event.key !== null && event.key !== "dsh-mobile.composer-collapse.enabled") return;
|
|
7196
|
+
listener(readComposerCollapseEnabled());
|
|
7197
|
+
};
|
|
7198
|
+
const onCustom = (event) => {
|
|
7199
|
+
const detail = event.detail;
|
|
7200
|
+
listener(detail?.enabled ?? readComposerCollapseEnabled());
|
|
7201
|
+
};
|
|
7202
|
+
globalThis.addEventListener?.("storage", onStorage);
|
|
7203
|
+
globalThis.addEventListener?.(COMPOSER_COLLAPSE_EVENT, onCustom);
|
|
7204
|
+
return () => {
|
|
7205
|
+
globalThis.removeEventListener?.("storage", onStorage);
|
|
7206
|
+
globalThis.removeEventListener?.(COMPOSER_COLLAPSE_EVENT, onCustom);
|
|
7207
|
+
};
|
|
7208
|
+
}
|
|
7209
|
+
// src/composer-collapse-settings.ts
|
|
7210
|
+
const COMPOSER_COLLAPSE_COPY = {
|
|
7211
|
+
en: {
|
|
7212
|
+
title: "Reading mode: collapse composer",
|
|
7213
|
+
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.",
|
|
7214
|
+
on: "On",
|
|
7215
|
+
off: "Off"
|
|
7216
|
+
},
|
|
7217
|
+
zh: {
|
|
7218
|
+
title: "阅读模式:收起输入栏",
|
|
7219
|
+
desc: "向下滚动阅读长会话时收起输入框编辑区;点按、聚焦或向上回滚即恢复。有草稿或回复生成中时不会收起。",
|
|
7220
|
+
on: "已开启",
|
|
7221
|
+
off: "已关闭"
|
|
7222
|
+
},
|
|
7223
|
+
it: {
|
|
7224
|
+
title: "Modalità lettura: comprimi il campo",
|
|
7225
|
+
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.",
|
|
7226
|
+
on: "Attivo",
|
|
7227
|
+
off: "Disattivo"
|
|
7228
|
+
}
|
|
7229
|
+
};
|
|
7230
|
+
/** Browser-only settings row; toggling it only persists the flag. */
|
|
7231
|
+
function ComposerCollapseSettings({ locale, read, write, subscribe }) {
|
|
7232
|
+
const readEnabled = read ?? (() => readComposerCollapseEnabled());
|
|
7233
|
+
const writeEnabled = write ?? ((enabled) => {
|
|
7234
|
+
writeComposerCollapseEnabled(enabled);
|
|
7235
|
+
});
|
|
7236
|
+
const enabled = (0, react.useSyncExternalStore)(subscribe ?? ((listener) => subscribeComposerCollapseEnabled(listener)), readEnabled, () => false);
|
|
7237
|
+
const copy = COMPOSER_COLLAPSE_COPY[locale];
|
|
7238
|
+
return (0, react.createElement)("label", {
|
|
7239
|
+
className: "dsh-mobile-settings_row",
|
|
7240
|
+
lang: locale === "zh" ? "zh-CN" : locale,
|
|
7241
|
+
"data-dsh-mobile-composer-collapse": enabled ? "on" : "off"
|
|
7242
|
+
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", {
|
|
7243
|
+
className: "dsh-mobile-settings_desc",
|
|
7244
|
+
"aria-live": "polite"
|
|
7245
|
+
}, copy.desc)), (0, react.createElement)("input", {
|
|
7246
|
+
type: "checkbox",
|
|
7247
|
+
role: "switch",
|
|
7248
|
+
className: "dsh-mobile-settings_switchInput",
|
|
7249
|
+
checked: enabled,
|
|
7250
|
+
"aria-label": copy.title,
|
|
7251
|
+
"aria-checked": enabled ? "true" : "false",
|
|
7252
|
+
onChange: (event) => {
|
|
7253
|
+
writeEnabled(event.currentTarget.checked);
|
|
7254
|
+
}
|
|
7255
|
+
}), (0, react.createElement)("span", {
|
|
7256
|
+
className: "dsh-mobile-settings_selector",
|
|
7257
|
+
"aria-hidden": "true"
|
|
7258
|
+
}, enabled ? copy.on : copy.off));
|
|
7259
|
+
}
|
|
7101
7260
|
// src/mobile-display-scale.ts
|
|
7102
7261
|
/** Native App page scaling in DSH General settings, independent from local typography. */
|
|
7103
7262
|
/** Validate the native reply before displaying or reusing its values in a request. */
|
|
@@ -9945,6 +10104,104 @@ window.__ModuleLoader__.load({
|
|
|
9945
10104
|
disabled: true
|
|
9946
10105
|
}, copy.busy)));
|
|
9947
10106
|
}
|
|
10107
|
+
// src/session-prefetch.ts
|
|
10108
|
+
/**
|
|
10109
|
+
* Storage key for the opt-in switch.
|
|
10110
|
+
*
|
|
10111
|
+
* The setting row lives in the client bundle while the tracker lives in the
|
|
10112
|
+
* layout bundle (tsdown.config.ts emits them separately and inlines this module
|
|
10113
|
+
* into each), so a module-level singleton cannot carry the value across. The
|
|
10114
|
+
* layout bundle owns the persisted state instead; the row notifies it through
|
|
10115
|
+
* both the storage event (other tabs) and a custom event (same page).
|
|
10116
|
+
*/
|
|
10117
|
+
const SESSION_PREFETCH_STORAGE_KEY = "dsh-mobile.session-prefetch.enabled";
|
|
10118
|
+
const SESSION_PREFETCH_EVENT = "dsh-mobile:session-prefetch-change";
|
|
10119
|
+
/** Read the persisted opt-in. Defaults to off; unreadable storage keeps it off. */
|
|
10120
|
+
function readSessionPrefetchEnabled(storage) {
|
|
10121
|
+
try {
|
|
10122
|
+
return (storage ?? globalThis.localStorage)?.getItem(SESSION_PREFETCH_STORAGE_KEY) === "true";
|
|
10123
|
+
} catch {
|
|
10124
|
+
return false;
|
|
10125
|
+
}
|
|
10126
|
+
}
|
|
10127
|
+
/** Persist the opt-in and notify the layout bundle in the same page. */
|
|
10128
|
+
function writeSessionPrefetchEnabled(enabled, storage) {
|
|
10129
|
+
try {
|
|
10130
|
+
(storage ?? globalThis.localStorage)?.setItem(SESSION_PREFETCH_STORAGE_KEY, String(enabled));
|
|
10131
|
+
} catch {}
|
|
10132
|
+
try {
|
|
10133
|
+
globalThis.dispatchEvent?.(new CustomEvent(SESSION_PREFETCH_EVENT, { detail: { enabled } }));
|
|
10134
|
+
} catch {}
|
|
10135
|
+
}
|
|
10136
|
+
/**
|
|
10137
|
+
* Subscribe to opt-in changes coming from either bundle. The listener is not
|
|
10138
|
+
* called with the initial value; read it once with readSessionPrefetchEnabled().
|
|
10139
|
+
*/
|
|
10140
|
+
function subscribeSessionPrefetchEnabled(listener) {
|
|
10141
|
+
const onStorage = (event) => {
|
|
10142
|
+
if (event.key !== null && event.key !== "dsh-mobile.session-prefetch.enabled") return;
|
|
10143
|
+
listener(readSessionPrefetchEnabled());
|
|
10144
|
+
};
|
|
10145
|
+
const onCustom = (event) => {
|
|
10146
|
+
const detail = event.detail;
|
|
10147
|
+
listener(detail?.enabled ?? readSessionPrefetchEnabled());
|
|
10148
|
+
};
|
|
10149
|
+
globalThis.addEventListener?.("storage", onStorage);
|
|
10150
|
+
globalThis.addEventListener?.(SESSION_PREFETCH_EVENT, onCustom);
|
|
10151
|
+
return () => {
|
|
10152
|
+
globalThis.removeEventListener?.("storage", onStorage);
|
|
10153
|
+
globalThis.removeEventListener?.(SESSION_PREFETCH_EVENT, onCustom);
|
|
10154
|
+
};
|
|
10155
|
+
}
|
|
10156
|
+
// src/session-prefetch-settings.ts
|
|
10157
|
+
const SESSION_PREFETCH_COPY = {
|
|
10158
|
+
en: {
|
|
10159
|
+
title: "Prefetch the session you just left",
|
|
10160
|
+
desc: "After switching sessions, warm the previous one in the background so switching back opens faster. Read-only, at most one session per switch, with a per-session cooldown.",
|
|
10161
|
+
on: "On",
|
|
10162
|
+
off: "Off"
|
|
10163
|
+
},
|
|
10164
|
+
zh: {
|
|
10165
|
+
title: "预取刚离开的会话",
|
|
10166
|
+
desc: "切换会话后在后台预热上一个会话,切回去时打开更快。只读、每次切换最多预热一个会话,并带单会话冷却。",
|
|
10167
|
+
on: "已开启",
|
|
10168
|
+
off: "已关闭"
|
|
10169
|
+
},
|
|
10170
|
+
it: {
|
|
10171
|
+
title: "Prefetch della sessione appena lasciata",
|
|
10172
|
+
desc: "Dopo un cambio di sessione riscalda in background quella precedente, così tornare indietro è più rapido. Sola lettura, al massimo una sessione per cambio, con cooldown per sessione.",
|
|
10173
|
+
on: "Attivo",
|
|
10174
|
+
off: "Disattivo"
|
|
10175
|
+
}
|
|
10176
|
+
};
|
|
10177
|
+
/** Browser-only settings row; toggling it only persists the flag. */
|
|
10178
|
+
function SessionPrefetchSettings({ locale, read, write, subscribe }) {
|
|
10179
|
+
const readEnabled = read ?? readSessionPrefetchEnabled;
|
|
10180
|
+
const writeEnabled = write ?? writeSessionPrefetchEnabled;
|
|
10181
|
+
const enabled = (0, react.useSyncExternalStore)(subscribe ?? subscribeSessionPrefetchEnabled, readEnabled, () => false);
|
|
10182
|
+
const copy = SESSION_PREFETCH_COPY[locale];
|
|
10183
|
+
return (0, react.createElement)("label", {
|
|
10184
|
+
className: "dsh-mobile-settings_row",
|
|
10185
|
+
lang: locale === "zh" ? "zh-CN" : locale,
|
|
10186
|
+
"data-dsh-mobile-session-prefetch": enabled ? "on" : "off"
|
|
10187
|
+
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", {
|
|
10188
|
+
className: "dsh-mobile-settings_desc",
|
|
10189
|
+
"aria-live": "polite"
|
|
10190
|
+
}, copy.desc)), (0, react.createElement)("input", {
|
|
10191
|
+
type: "checkbox",
|
|
10192
|
+
role: "switch",
|
|
10193
|
+
className: "dsh-mobile-settings_switchInput",
|
|
10194
|
+
checked: enabled,
|
|
10195
|
+
"aria-label": copy.title,
|
|
10196
|
+
"aria-checked": enabled ? "true" : "false",
|
|
10197
|
+
onChange: (event) => {
|
|
10198
|
+
writeEnabled(event.currentTarget.checked);
|
|
10199
|
+
}
|
|
10200
|
+
}), (0, react.createElement)("span", {
|
|
10201
|
+
className: "dsh-mobile-settings_selector",
|
|
10202
|
+
"aria-hidden": "true"
|
|
10203
|
+
}, enabled ? copy.on : copy.off));
|
|
10204
|
+
}
|
|
9948
10205
|
// src/mobile-push/navigation.ts
|
|
9949
10206
|
/**
|
|
9950
10207
|
* Consume one authenticated tap reference after the public Workspace and Session
|
|
@@ -11458,7 +11715,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11458
11715
|
enabled: "关闭后台提醒",
|
|
11459
11716
|
disabled: "开启后台提醒",
|
|
11460
11717
|
busy: "正在处理…",
|
|
11461
|
-
error: "无法更新后台提醒;请确认任务通知权限和当前连接。"
|
|
11718
|
+
error: "无法更新后台提醒;请确认任务通知权限和当前连接。",
|
|
11719
|
+
connected: "已连接",
|
|
11720
|
+
disconnected: "未连接"
|
|
11462
11721
|
};
|
|
11463
11722
|
if (locale === "it") return {
|
|
11464
11723
|
title: "Promemoria in background",
|
|
@@ -11466,7 +11725,9 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11466
11725
|
enabled: "Disattiva promemoria",
|
|
11467
11726
|
disabled: "Attiva promemoria",
|
|
11468
11727
|
busy: "Operazione…",
|
|
11469
|
-
error: "Impossibile aggiornare i promemoria; controlla il permesso notifiche e la connessione attiva."
|
|
11728
|
+
error: "Impossibile aggiornare i promemoria; controlla il permesso notifiche e la connessione attiva.",
|
|
11729
|
+
connected: "Connesso",
|
|
11730
|
+
disconnected: "Non connesso"
|
|
11470
11731
|
};
|
|
11471
11732
|
return {
|
|
11472
11733
|
title: "Background task reminders",
|
|
@@ -11474,13 +11735,24 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11474
11735
|
enabled: "Disable background reminders",
|
|
11475
11736
|
disabled: "Enable background reminders",
|
|
11476
11737
|
busy: "Working…",
|
|
11477
|
-
error: "Could not update background reminders. Check notification permission and the active connection."
|
|
11738
|
+
error: "Could not update background reminders. Check notification permission and the active connection.",
|
|
11739
|
+
connected: "Connected",
|
|
11740
|
+
disconnected: "Not connected"
|
|
11478
11741
|
};
|
|
11479
11742
|
}
|
|
11743
|
+
/** Render the App's real connection state when the installed App reports it. */
|
|
11744
|
+
function mobileBackgroundStatusText(locale, connected, lastError) {
|
|
11745
|
+
if (connected === void 0) return void 0;
|
|
11746
|
+
const copy = mobileBackgroundNotificationCopy(locale);
|
|
11747
|
+
if (connected) return copy.connected;
|
|
11748
|
+
return lastError === void 0 ? copy.disconnected : `${copy.disconnected} (${lastError})`;
|
|
11749
|
+
}
|
|
11480
11750
|
/** Android-only opt-in for the foreground SSE fallback when the WebView is suspended. */
|
|
11481
11751
|
function MobileBackgroundNotificationRow() {
|
|
11482
11752
|
const [supported, setSupported] = (0, react.useState)(false);
|
|
11483
11753
|
const [enabled, setEnabled] = (0, react.useState)(false);
|
|
11754
|
+
const [connected, setConnected] = (0, react.useState)(void 0);
|
|
11755
|
+
const [lastError, setLastError] = (0, react.useState)(void 0);
|
|
11484
11756
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
11485
11757
|
const [error, setError] = (0, react.useState)(false);
|
|
11486
11758
|
const generation = (0, react.useRef)(0);
|
|
@@ -11505,6 +11777,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11505
11777
|
if (!active || attempt !== generation.current) return;
|
|
11506
11778
|
if (typeof result?.enabled !== "boolean") throw new Error("invalid background notification status");
|
|
11507
11779
|
setEnabled(result.enabled);
|
|
11780
|
+
setConnected(typeof result.connected === "boolean" ? result.connected : void 0);
|
|
11781
|
+
setLastError(typeof result.lastError === "string" ? result.lastError : void 0);
|
|
11508
11782
|
setError(false);
|
|
11509
11783
|
}).catch(() => {
|
|
11510
11784
|
if (active && attempt === generation.current) {
|
|
@@ -11524,6 +11798,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11524
11798
|
if (!supported) return null;
|
|
11525
11799
|
const locale = selectedMobileControlLocale();
|
|
11526
11800
|
const copy = mobileBackgroundNotificationCopy(locale);
|
|
11801
|
+
const statusText = enabled ? mobileBackgroundStatusText(locale, connected, lastError) : void 0;
|
|
11527
11802
|
const toggle = () => {
|
|
11528
11803
|
const bridge = window.__DSH_MOBILE_NATIVE__;
|
|
11529
11804
|
if (bridge === void 0 || busy) return;
|
|
@@ -11535,6 +11810,13 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11535
11810
|
const next = result?.enabled;
|
|
11536
11811
|
if (typeof next !== "boolean") throw new Error("invalid background notification status");
|
|
11537
11812
|
setEnabled(next);
|
|
11813
|
+
bridge.invoke("notification.background.get", {}).then((statusResult) => {
|
|
11814
|
+
if (attempt !== generation.current) return;
|
|
11815
|
+
const status = statusResult;
|
|
11816
|
+
if (typeof status?.enabled === "boolean") setEnabled(status.enabled);
|
|
11817
|
+
setConnected(typeof status?.connected === "boolean" ? status.connected : void 0);
|
|
11818
|
+
setLastError(typeof status?.lastError === "string" ? status.lastError : void 0);
|
|
11819
|
+
}).catch(() => void 0);
|
|
11538
11820
|
}).catch(() => {
|
|
11539
11821
|
if (attempt === generation.current) setError(true);
|
|
11540
11822
|
}).finally(() => {
|
|
@@ -11547,7 +11829,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
11547
11829
|
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", {
|
|
11548
11830
|
className: "dsh-mobile-settings_desc",
|
|
11549
11831
|
"aria-live": "polite"
|
|
11550
|
-
}, error ? copy.error : copy.description)), (0, react.createElement)("button", {
|
|
11832
|
+
}, error ? copy.error : statusText ?? copy.description)), (0, react.createElement)("button", {
|
|
11551
11833
|
type: "button",
|
|
11552
11834
|
className: "dsh-mobile-settings_selector",
|
|
11553
11835
|
disabled: busy,
|
|
@@ -17656,6 +17938,45 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
17656
17938
|
fiber.dispose();
|
|
17657
17939
|
};
|
|
17658
17940
|
}
|
|
17941
|
+
/**
|
|
17942
|
+
* Open a Session in the live page from an Android notification tap. The App calls this through
|
|
17943
|
+
* `window.__dshMobileOpenSession` instead of reloading the document; returning false asks the App
|
|
17944
|
+
* to fall back to a route navigation (for example while the page is still booting).
|
|
17945
|
+
*/
|
|
17946
|
+
function installNativeNotificationNavigation(ctx, view) {
|
|
17947
|
+
if (view.__DSH_MOBILE_FRONTEND__ !== "dedicated" || ctx.inject === void 0) return () => void 0;
|
|
17948
|
+
let closed = false;
|
|
17949
|
+
const target = view;
|
|
17950
|
+
const fiber = ctx.inject(mobilePushNavigationServices, (navigationContext) => {
|
|
17951
|
+
if (closed) return () => void 0;
|
|
17952
|
+
const context = navigationContext;
|
|
17953
|
+
const open = (route) => {
|
|
17954
|
+
if (closed || typeof route !== "string") return false;
|
|
17955
|
+
const sessionId = sessionIdFromNotificationRoute(route);
|
|
17956
|
+
if (sessionId === void 0) return false;
|
|
17957
|
+
const workspace = context.uiWorkspace;
|
|
17958
|
+
if (workspace === void 0 || typeof workspace.openSession !== "function") return false;
|
|
17959
|
+
const sessions = context.sessions?.list.getSnapshot();
|
|
17960
|
+
if (sessions !== void 0 && sessions.phase === "ready" && !sessions.ids.includes(sessionId)) return false;
|
|
17961
|
+
try {
|
|
17962
|
+
workspace.openSession(sessionId);
|
|
17963
|
+
return true;
|
|
17964
|
+
} catch {
|
|
17965
|
+
return false;
|
|
17966
|
+
}
|
|
17967
|
+
};
|
|
17968
|
+
target.__dshMobileOpenSession = open;
|
|
17969
|
+
return () => {
|
|
17970
|
+
if (target.__dshMobileOpenSession === open) delete target.__dshMobileOpenSession;
|
|
17971
|
+
};
|
|
17972
|
+
});
|
|
17973
|
+
return () => {
|
|
17974
|
+
if (closed) return;
|
|
17975
|
+
closed = true;
|
|
17976
|
+
delete target.__dshMobileOpenSession;
|
|
17977
|
+
fiber.dispose();
|
|
17978
|
+
};
|
|
17979
|
+
}
|
|
17659
17980
|
/** Own the browser-only push controller, settings row, and optional authenticated tap navigation. */
|
|
17660
17981
|
function installMobilePushBrowserClient(ctx, runtime) {
|
|
17661
17982
|
const { page, view } = runtime;
|
|
@@ -17729,6 +18050,19 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
17729
18050
|
controller.stop();
|
|
17730
18051
|
};
|
|
17731
18052
|
}
|
|
18053
|
+
/**
|
|
18054
|
+
* Register the session-prefetch opt-in on every dedicated Mobile page, including the Android
|
|
18055
|
+
* App webview. The row used to live inside the browser-push installer, which returns early when
|
|
18056
|
+
* the native bridge exists, so App users never saw the switch for a layout-side feature they use.
|
|
18057
|
+
*/
|
|
18058
|
+
function installSessionPrefetchSettings(ctx, view) {
|
|
18059
|
+
if (view.__DSH_MOBILE_FRONTEND__ !== "dedicated") return () => void 0;
|
|
18060
|
+
return ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
18061
|
+
name: "settings.general.item",
|
|
18062
|
+
id: "dsh-mobile-session-prefetch",
|
|
18063
|
+
order: 130
|
|
18064
|
+
}, () => (0, react.createElement)(SessionPrefetchSettings, { locale: selectedMobileControlLocale() })));
|
|
18065
|
+
}
|
|
17732
18066
|
/** Mount the desktop control or mobile feature enhancements. */
|
|
17733
18067
|
function apply(ctx) {
|
|
17734
18068
|
let gatewayRejectionSink;
|
|
@@ -17768,6 +18102,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
17768
18102
|
view: window
|
|
17769
18103
|
}), "dsh-mobile: authenticated browser push lifecycle");
|
|
17770
18104
|
ctx.effect(() => installTaskNotificationNavigation(ctx, document, window), "dsh-mobile: native task notification navigation");
|
|
18105
|
+
ctx.effect(() => installNativeNotificationNavigation(ctx, window), "dsh-mobile: in-page notification navigation");
|
|
18106
|
+
ctx.effect(() => installSessionPrefetchSettings(ctx, window), "dsh-mobile: session prefetch settings");
|
|
17771
18107
|
ctx.effect(() => {
|
|
17772
18108
|
const desktopAdmin = isDesktopAdminSurface(location.hostname, location.search, window.__DSH_MOBILE_FRONTEND__, location.protocol);
|
|
17773
18109
|
const gatewayCustomization = ownsMobileCustomization(document.querySelector("meta[name=\"dsh-mobile-gateway\"][content=\"authenticated\"]") !== null, window.__DSH_MOBILE_FRONTEND__);
|
|
@@ -17810,6 +18146,11 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
17810
18146
|
preference: localFont.preference,
|
|
17811
18147
|
locale: selectedMobileControlLocale()
|
|
17812
18148
|
}));
|
|
18149
|
+
const removeComposerCollapse = ctx.slots.register({
|
|
18150
|
+
name: "settings.general.item",
|
|
18151
|
+
id: "dsh-mobile-composer-collapse",
|
|
18152
|
+
order: 15
|
|
18153
|
+
}, () => (0, react.createElement)(ComposerCollapseSettings, { locale: selectedMobileControlLocale() }));
|
|
17813
18154
|
const removeDisplayScale = ctx.slots.register({
|
|
17814
18155
|
name: "settings.general.item",
|
|
17815
18156
|
id: "dsh-mobile-display-scale",
|
|
@@ -17835,6 +18176,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
17835
18176
|
removeTaskNotifications();
|
|
17836
18177
|
removeSwitchComputer();
|
|
17837
18178
|
removeDisplayScale();
|
|
18179
|
+
removeComposerCollapse();
|
|
17838
18180
|
removeFontSize();
|
|
17839
18181
|
};
|
|
17840
18182
|
});
|
|
@@ -17944,15 +18286,19 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
17944
18286
|
exports.installDshLanguageBoundSurface = installDshLanguageBoundSurface;
|
|
17945
18287
|
exports.installMobileNativeBack = installMobileNativeBack;
|
|
17946
18288
|
exports.installMobilePushBrowserClient = installMobilePushBrowserClient;
|
|
18289
|
+
exports.installNativeNotificationNavigation = installNativeNotificationNavigation;
|
|
18290
|
+
exports.installSessionPrefetchSettings = installSessionPrefetchSettings;
|
|
17947
18291
|
exports.installTaskNotificationNavigation = installTaskNotificationNavigation;
|
|
17948
18292
|
exports.localizedDiagnosticEntryCopy = localizedDiagnosticEntryCopy;
|
|
17949
18293
|
exports.mobileBackBlockedMessage = mobileBackBlockedMessage;
|
|
18294
|
+
exports.mobileBackgroundStatusText = mobileBackgroundStatusText;
|
|
17950
18295
|
exports.mobilePushNavigationNotice = mobilePushNavigationNotice;
|
|
17951
18296
|
exports.normalizeDiagnosticOverall = normalizeDiagnosticOverall;
|
|
17952
18297
|
exports.normalizeDiagnosticStatus = normalizeDiagnosticStatus;
|
|
17953
18298
|
exports.parseMobileExtensionManifest = parseMobileExtensionManifest;
|
|
17954
18299
|
exports.parseMobileRemoteProvider = parseMobileRemoteProvider;
|
|
17955
18300
|
exports.publishAuthoritativeExtensionIds = publishAuthoritativeExtensionIds;
|
|
18301
|
+
exports.readComposerCollapseEnabled = readComposerCollapseEnabled;
|
|
17956
18302
|
exports.reconcileRemovedExtensions = reconcileRemovedExtensions;
|
|
17957
18303
|
exports.registerUniqueDisposable = registerUniqueDisposable;
|
|
17958
18304
|
exports.renderDiagnosticPayloadSafely = renderDiagnosticPayloadSafely;
|
|
@@ -17965,6 +18311,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
17965
18311
|
exports.startLifecycleRefreshScheduler = startLifecycleRefreshScheduler;
|
|
17966
18312
|
exports.trustAuthenticatedGatewayConnection = trustAuthenticatedGatewayConnection;
|
|
17967
18313
|
exports.validateDiagnosticChecks = validateDiagnosticChecks;
|
|
18314
|
+
exports.writeComposerCollapseEnabled = writeComposerCollapseEnabled;
|
|
17968
18315
|
return module.exports;
|
|
17969
18316
|
}
|
|
17970
18317
|
});
|