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/mobile-layout.js
CHANGED
|
@@ -24,19 +24,286 @@ window.__ModuleLoader__.load({
|
|
|
24
24
|
it: Object.freeze({
|
|
25
25
|
closePanels: "Chiudi pannelli",
|
|
26
26
|
workspaceNavigation: "Navigazione area di lavoro e sessioni",
|
|
27
|
-
backToConversation: "Torna alla conversazione"
|
|
27
|
+
backToConversation: "Torna alla conversazione",
|
|
28
|
+
composerRestore: "Mostra il campo di scrittura"
|
|
28
29
|
}),
|
|
29
30
|
en: Object.freeze({
|
|
30
31
|
closePanels: "Close panels",
|
|
31
32
|
workspaceNavigation: "Workspace and session navigation",
|
|
32
|
-
backToConversation: "Back to conversation"
|
|
33
|
+
backToConversation: "Back to conversation",
|
|
34
|
+
composerRestore: "Show composer"
|
|
33
35
|
}),
|
|
34
36
|
zh: Object.freeze({
|
|
35
37
|
closePanels: "关闭浮层",
|
|
36
38
|
workspaceNavigation: "工作区与会话导航",
|
|
37
|
-
backToConversation: "返回会话"
|
|
39
|
+
backToConversation: "返回会话",
|
|
40
|
+
composerRestore: "展开输入框"
|
|
38
41
|
})
|
|
39
42
|
});
|
|
43
|
+
// src/composer-collapse.ts
|
|
44
|
+
const COMPOSER_COLLAPSE_DEFAULTS = {
|
|
45
|
+
enabled: false,
|
|
46
|
+
collapseThresholdPx: 480,
|
|
47
|
+
reverseResetPx: 80
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Track cumulative reading progress across scroll events. This is the only
|
|
51
|
+
* collapse policy: a decision table plus accumulated progress, kept out of the
|
|
52
|
+
* component so it stays testable without a DOM.
|
|
53
|
+
*/
|
|
54
|
+
var ComposerCollapseTracker = class {
|
|
55
|
+
readingProgress = 0;
|
|
56
|
+
reverseProgress = 0;
|
|
57
|
+
state = "expanded";
|
|
58
|
+
config;
|
|
59
|
+
constructor(config) {
|
|
60
|
+
this.config = config;
|
|
61
|
+
}
|
|
62
|
+
/** Whether the opt-in switch is currently on. */
|
|
63
|
+
get enabled() {
|
|
64
|
+
return this.config.enabled;
|
|
65
|
+
}
|
|
66
|
+
/** Update whether collapse is enabled (the user's settings toggle). */
|
|
67
|
+
setEnabled(enabled) {
|
|
68
|
+
this.config = {
|
|
69
|
+
...this.config,
|
|
70
|
+
enabled
|
|
71
|
+
};
|
|
72
|
+
if (!enabled) this.reset();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Feed one scroll sample; returns the resulting presentation state.
|
|
76
|
+
*
|
|
77
|
+
* reverseResetPx is a deliberate dead zone: a collapsed composer stays
|
|
78
|
+
* collapsed until the user scrolls back up at least that far, so a 1px jitter
|
|
79
|
+
* does not flip the layout on every frame.
|
|
80
|
+
*/
|
|
81
|
+
sample(deltaY) {
|
|
82
|
+
if (!this.config.enabled) {
|
|
83
|
+
this.reset();
|
|
84
|
+
return this.state;
|
|
85
|
+
}
|
|
86
|
+
if (this.state === "collapsed") {
|
|
87
|
+
if (deltaY > 0) {
|
|
88
|
+
this.reverseProgress = 0;
|
|
89
|
+
return this.state;
|
|
90
|
+
}
|
|
91
|
+
this.reverseProgress += -deltaY;
|
|
92
|
+
if (this.reverseProgress >= Math.max(this.config.reverseResetPx, 1)) this.reset();
|
|
93
|
+
return this.state;
|
|
94
|
+
}
|
|
95
|
+
if (deltaY < 0) {
|
|
96
|
+
this.readingProgress = Math.max(0, this.readingProgress + deltaY);
|
|
97
|
+
this.reverseProgress = 0;
|
|
98
|
+
return this.state;
|
|
99
|
+
}
|
|
100
|
+
this.readingProgress += deltaY;
|
|
101
|
+
this.reverseProgress = 0;
|
|
102
|
+
if (this.readingProgress >= this.config.collapseThresholdPx) this.state = "collapsed";
|
|
103
|
+
return this.state;
|
|
104
|
+
}
|
|
105
|
+
/** User interacted with the composer (focus, tap, keyboard, draft): expand. */
|
|
106
|
+
expand() {
|
|
107
|
+
this.reset();
|
|
108
|
+
}
|
|
109
|
+
get stateValue() {
|
|
110
|
+
return this.state;
|
|
111
|
+
}
|
|
112
|
+
/** @internal exposed for tests */
|
|
113
|
+
progress() {
|
|
114
|
+
return this.readingProgress;
|
|
115
|
+
}
|
|
116
|
+
reset() {
|
|
117
|
+
this.state = "expanded";
|
|
118
|
+
this.readingProgress = 0;
|
|
119
|
+
this.reverseProgress = 0;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const COMPOSER_COLLAPSE_EVENT = "dsh-mobile:composer-collapse-change";
|
|
123
|
+
/** Read the persisted opt-in. Defaults to off; unreadable storage keeps it off. */
|
|
124
|
+
function readComposerCollapseEnabled(storage) {
|
|
125
|
+
try {
|
|
126
|
+
const stored = (storage === void 0 ? globalThis.localStorage : storage)?.getItem("dsh-mobile.composer-collapse.enabled") ?? null;
|
|
127
|
+
if (stored !== null) return stored === "true";
|
|
128
|
+
return storage === void 0 ? false : false;
|
|
129
|
+
} catch {
|
|
130
|
+
return storage === void 0 ? false : false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Subscribe to opt-in changes coming from either bundle. The listener is not
|
|
135
|
+
* called with the initial value; read it once with readComposerCollapseEnabled().
|
|
136
|
+
*/
|
|
137
|
+
function subscribeComposerCollapseEnabled(listener) {
|
|
138
|
+
const onStorage = (event) => {
|
|
139
|
+
if (event.key !== null && event.key !== "dsh-mobile.composer-collapse.enabled") return;
|
|
140
|
+
listener(readComposerCollapseEnabled());
|
|
141
|
+
};
|
|
142
|
+
const onCustom = (event) => {
|
|
143
|
+
const detail = event.detail;
|
|
144
|
+
listener(detail?.enabled ?? readComposerCollapseEnabled());
|
|
145
|
+
};
|
|
146
|
+
globalThis.addEventListener?.("storage", onStorage);
|
|
147
|
+
globalThis.addEventListener?.(COMPOSER_COLLAPSE_EVENT, onCustom);
|
|
148
|
+
return () => {
|
|
149
|
+
globalThis.removeEventListener?.("storage", onStorage);
|
|
150
|
+
globalThis.removeEventListener?.(COMPOSER_COLLAPSE_EVENT, onCustom);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** Apply the persisted opt-in onto a base config. */
|
|
154
|
+
function composerCollapseConfigFrom(enabled, base = COMPOSER_COLLAPSE_DEFAULTS) {
|
|
155
|
+
return {
|
|
156
|
+
...base,
|
|
157
|
+
enabled
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 软键盘开合监听。
|
|
162
|
+
* 键盘弹起(visualViewport 缩小)意味着用户要输入,应展开收起态。
|
|
163
|
+
* 封装在这里而不是 layout,是为了让 layout 源码不含 visualViewport——
|
|
164
|
+
* 那里只用 window.innerWidth 判宽屏,否则软键盘缩小可视视口会把手机误判成宽屏。
|
|
165
|
+
*/
|
|
166
|
+
function onViewportResize(handler) {
|
|
167
|
+
const viewport = globalThis.visualViewport;
|
|
168
|
+
if (viewport === null || viewport === void 0) return () => {};
|
|
169
|
+
viewport.addEventListener("resize", handler);
|
|
170
|
+
return () => {
|
|
171
|
+
viewport.removeEventListener("resize", handler);
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const COLLAPSED_ATTRIBUTE = "data-dsh-mobile-composer-collapsed";
|
|
175
|
+
const RESTORE_LABEL_PROPERTY = "--dshm-composer-restore-label";
|
|
176
|
+
const CONVERSATION_SCROLL_SELECTOR = "[data-conversation-scroll]";
|
|
177
|
+
const COMPOSER_CARD_SELECTOR$1 = "[data-composer-card]";
|
|
178
|
+
const COMPOSER_EDITOR_SELECTOR$1 = `${COMPOSER_CARD_SELECTOR$1} :is([contenteditable="true"],[contenteditable="plaintext-only"],[contenteditable=""],textarea)`;
|
|
179
|
+
/**
|
|
180
|
+
* Install the reading-mode collapse. Opt-in through the persisted switch, off by
|
|
181
|
+
* default; the composer and its draft stay owned by DSH, this only toggles a
|
|
182
|
+
* presentation attribute on the document element.
|
|
183
|
+
*/
|
|
184
|
+
function installComposerReadingMode(options) {
|
|
185
|
+
const doc = options.doc ?? globalThis.document;
|
|
186
|
+
const root = doc.documentElement;
|
|
187
|
+
const tracker = new ComposerCollapseTracker(composerCollapseConfigFrom(readComposerCollapseEnabled()));
|
|
188
|
+
let collapsed = false;
|
|
189
|
+
const view = doc.defaultView;
|
|
190
|
+
const apply = (next) => {
|
|
191
|
+
if (collapsed === next) return;
|
|
192
|
+
collapsed = next;
|
|
193
|
+
root.setAttribute(COLLAPSED_ATTRIBUTE, next ? "true" : "false");
|
|
194
|
+
};
|
|
195
|
+
const expand = () => {
|
|
196
|
+
tracker.expand();
|
|
197
|
+
apply(false);
|
|
198
|
+
};
|
|
199
|
+
root.style.setProperty(RESTORE_LABEL_PROPERTY, JSON.stringify(options.restoreLabel));
|
|
200
|
+
const scroller = () => {
|
|
201
|
+
const found = doc.querySelector(CONVERSATION_SCROLL_SELECTOR);
|
|
202
|
+
return found instanceof (view?.HTMLElement ?? Object) ? found : void 0;
|
|
203
|
+
};
|
|
204
|
+
let lastTop = scroller()?.scrollTop ?? 0;
|
|
205
|
+
let rafPending = false;
|
|
206
|
+
const sample = () => {
|
|
207
|
+
rafPending = false;
|
|
208
|
+
const port = scroller();
|
|
209
|
+
if (port === void 0) return;
|
|
210
|
+
const top = port.scrollTop;
|
|
211
|
+
const delta = top - lastTop;
|
|
212
|
+
lastTop = top;
|
|
213
|
+
if (busy()) {
|
|
214
|
+
expand();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
apply(tracker.sample(delta) === "collapsed");
|
|
218
|
+
};
|
|
219
|
+
const onScroll = (event) => {
|
|
220
|
+
const target = event.target;
|
|
221
|
+
if (target === null || !target.hasAttribute?.(CONVERSATION_SCROLL_SELECTOR.slice(1, -1))) return;
|
|
222
|
+
if (rafPending) return;
|
|
223
|
+
rafPending = true;
|
|
224
|
+
if (view === null) {
|
|
225
|
+
sample();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
view.requestAnimationFrame(sample);
|
|
229
|
+
};
|
|
230
|
+
const busy = () => {
|
|
231
|
+
if (options.isRunning()) return true;
|
|
232
|
+
const card = doc.querySelector(COMPOSER_CARD_SELECTOR$1);
|
|
233
|
+
const editor = card?.querySelector(COMPOSER_EDITOR_SELECTOR$1) ?? null;
|
|
234
|
+
if (editor === null) return false;
|
|
235
|
+
const phase = editor.dataset.phase ?? "";
|
|
236
|
+
if (phase !== "" && phase !== "plain") return true;
|
|
237
|
+
const focused = doc.activeElement;
|
|
238
|
+
if (focused !== null && card !== null && card.contains(focused)) return true;
|
|
239
|
+
return (editor.value ?? editor.textContent ?? "").trim() !== "";
|
|
240
|
+
};
|
|
241
|
+
const guard = () => {
|
|
242
|
+
if (busy()) expand();
|
|
243
|
+
};
|
|
244
|
+
const onPointerDown = (event) => {
|
|
245
|
+
if (!collapsed) return;
|
|
246
|
+
const target = event.target;
|
|
247
|
+
if (target === null || !(target instanceof (view?.Element ?? Object))) return;
|
|
248
|
+
if (target.closest(COMPOSER_CARD_SELECTOR$1) === null) return;
|
|
249
|
+
expand();
|
|
250
|
+
};
|
|
251
|
+
const onFocusIn = () => {
|
|
252
|
+
expand();
|
|
253
|
+
};
|
|
254
|
+
let observed = [];
|
|
255
|
+
const observeCards = () => {
|
|
256
|
+
const cards = [...doc.querySelectorAll(COMPOSER_CARD_SELECTOR$1)];
|
|
257
|
+
if (cards.length === observed.length && cards.every((card, index) => card === observed[index])) return;
|
|
258
|
+
observer.disconnect();
|
|
259
|
+
for (const card of cards) observer.observe(card, {
|
|
260
|
+
childList: true,
|
|
261
|
+
subtree: true,
|
|
262
|
+
characterData: true,
|
|
263
|
+
attributes: true,
|
|
264
|
+
attributeFilter: ["data-phase"]
|
|
265
|
+
});
|
|
266
|
+
observed = cards;
|
|
267
|
+
};
|
|
268
|
+
const observer = new ((view?.MutationObserver) ?? MutationObserver)(() => {
|
|
269
|
+
observeCards();
|
|
270
|
+
guard();
|
|
271
|
+
});
|
|
272
|
+
observeCards();
|
|
273
|
+
doc.addEventListener("scroll", onScroll, {
|
|
274
|
+
passive: true,
|
|
275
|
+
capture: true
|
|
276
|
+
});
|
|
277
|
+
doc.addEventListener("pointerdown", onPointerDown, true);
|
|
278
|
+
doc.addEventListener("focusin", onFocusIn, true);
|
|
279
|
+
const stopPreference = subscribeComposerCollapseEnabled((enabled) => {
|
|
280
|
+
tracker.setEnabled(enabled);
|
|
281
|
+
if (!enabled) expand();
|
|
282
|
+
});
|
|
283
|
+
const stopViewport = onViewportResize(onFocusIn);
|
|
284
|
+
return {
|
|
285
|
+
expand,
|
|
286
|
+
dispose() {
|
|
287
|
+
doc.removeEventListener("scroll", onScroll, { capture: true });
|
|
288
|
+
doc.removeEventListener("pointerdown", onPointerDown, true);
|
|
289
|
+
doc.removeEventListener("focusin", onFocusIn, true);
|
|
290
|
+
stopViewport();
|
|
291
|
+
stopPreference();
|
|
292
|
+
observer.disconnect();
|
|
293
|
+
root.style.removeProperty(RESTORE_LABEL_PROPERTY);
|
|
294
|
+
apply(false);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Whether the active session is streaming a reply. A running reply is not a
|
|
300
|
+
* composer phase: `data-phase` returns to `plain` as soon as the submission is
|
|
301
|
+
* accepted, so this flag is the only truthful signal for the streaming window.
|
|
302
|
+
*/
|
|
303
|
+
function selectActiveSessionRunning(state) {
|
|
304
|
+
const current = state.current;
|
|
305
|
+
return current !== void 0 && state.byId[current]?.running === true;
|
|
306
|
+
}
|
|
40
307
|
// src/session-prefetch.ts
|
|
41
308
|
const SESSION_PREFETCH_DEFAULTS = {
|
|
42
309
|
enabled: false,
|
|
@@ -650,9 +917,22 @@ html,body,#root{width:100%;height:100%;overflow:hidden}
|
|
|
650
917
|
.dshm-drawer{position:fixed;z-index:70;inset:0 auto 0 0;box-sizing:border-box;width:56px;max-width:100%;padding-top:env(safe-area-inset-top);overflow:hidden;background-color:Canvas;background-image:linear-gradient(var(--dsw-alias-bg-layer-1,#f8fafc),var(--dsw-alias-bg-layer-1,#f8fafc));box-shadow:none;will-change:width;transition:width 240ms cubic-bezier(.22,1,.36,1),box-shadow 240ms ease}
|
|
651
918
|
.dshm-drawer[data-open=true]{width:min(88vw,340px);box-shadow:18px 0 46px rgb(15 23 42 / 18%)}
|
|
652
919
|
.dshm-drawer[data-open=false]{pointer-events:auto;visibility:visible}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
920
|
+
/* Slide the sidebar content only while opening or closing. A resting transform
|
|
921
|
+
would make this child the containing block for stock fixed-position surfaces
|
|
922
|
+
inside the sidebar and let the drawer's overflow clip them (issue #413: the
|
|
923
|
+
Cordis plugin panel anchors itself to the viewport to escape that clip). */
|
|
924
|
+
.dshm-drawer>*{width:100%!important;height:100%!important;opacity:.94;transition:opacity 160ms ease-out}
|
|
925
|
+
.dshm-drawer[data-open=true]>*{opacity:1;animation:dshm-drawer-slide-open 220ms cubic-bezier(.22,1,.36,1)}
|
|
926
|
+
.dshm-drawer[data-open=false]>*{width:56px!important;animation:dshm-drawer-slide-closed 220ms cubic-bezier(.22,1,.36,1)}
|
|
927
|
+
@keyframes dshm-drawer-slide-open{from{transform:translateX(-6px)}to{transform:translateX(0)}}
|
|
928
|
+
@keyframes dshm-drawer-slide-closed{from{transform:translateX(0)}to{transform:translateX(-6px)}}
|
|
929
|
+
/* The Cordis plugin panel opens from the sidebar footer as a stock fixed surface
|
|
930
|
+
(position:fixed, width:420px, anchored from its trigger) so the sidebar's
|
|
931
|
+
overflow cannot clip it. On phones the drawer must give it real containment:
|
|
932
|
+
span the viewport instead of the desktop width, sit above our drawer but below
|
|
933
|
+
the stock floating slot layer (300), and cap the height so its body scrolls
|
|
934
|
+
(issue #413). */
|
|
935
|
+
.dshm-shell:not([${WIDE_LAYOUT_ATTRIBUTE}="1"]) [data-cordis-panel]{position:fixed!important;inset:auto 8px max(8px,env(safe-area-inset-bottom)) 8px!important;width:auto!important;max-width:min(420px,calc(100vw - 16px))!important;max-height:min(60vh,calc(100dvh - 96px))!important;overflow-y:auto!important;z-index:210!important}
|
|
656
936
|
/* Folded icon rail: on narrow viewports the drawer is fixed, so give the main
|
|
657
937
|
content the same 56px offset the wide grid already provides. The width floor
|
|
658
938
|
keeps phone-width headers on their own inset; the band itself is the root
|
|
@@ -727,7 +1007,7 @@ body:has(.dshm-shell) :is(input,textarea,[contenteditable=true],[contenteditable
|
|
|
727
1007
|
dims and covers it. Only the wide grid needs this: on a phone the drawer is a
|
|
728
1008
|
floating overlay and must keep covering the conversation. */
|
|
729
1009
|
.dshm-shell[${WIDE_LAYOUT_ATTRIBUTE}="1"] .dshm-main{grid-area:1/2;z-index:1}
|
|
730
|
-
@media(prefers-reduced-motion:reduce){.dshm-drawer,.dshm-details,.dshm-scrim,.dshm-drawer>*,.dshm-main{transition:none!important}}
|
|
1010
|
+
@media(prefers-reduced-motion:reduce){.dshm-drawer,.dshm-details,.dshm-scrim,.dshm-drawer>*,.dshm-main{transition:none!important}.dshm-drawer>*{animation:none!important}}
|
|
731
1011
|
/* Touch adaptation changes control boxes, not invisible hit regions. Root opt-out
|
|
732
1012
|
retains pane geometry too; nested opt-out preserves a widget's own controls.
|
|
733
1013
|
DSH's plugin cards open through a cardTitle/cardOpen button whose ::after
|
|
@@ -739,6 +1019,17 @@ body:has(.dshm-shell) :is(input,textarea,[contenteditable=true],[contenteditable
|
|
|
739
1019
|
.dshm-main[data-dsh-panel="1"] [data-slot="main"]>:not([data-dsh-adapt="off"]) :is(button,[role="button"],select,input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=hidden]):not([type=color]):not([type=file]):not([type=image])):not([role="switch"],[role="checkbox"],[role="radio"],[role="slider"],button[class*="_cardTitle"][class*="_cardOpen"],[data-dsh-adapt="off"],[data-dsh-adapt="off"] *){min-width:44px!important;min-height:44px!important}
|
|
740
1020
|
.dshm-main[data-dsh-panel="1"] [data-slot="main"]>:not([data-dsh-adapt="off"]) :is(button,[role="button"],a):not([data-dsh-adapt="off"],[data-dsh-adapt="off"] *){touch-action:manipulation}
|
|
741
1021
|
}
|
|
1022
|
+
/* Reading mode (issue #194): after scrolling down a long conversation the
|
|
1023
|
+
composer editor collapses so the reading area is unobstructed. The card stays
|
|
1024
|
+
and keeps the upstream Send/Stop row; its generated label is the way back, and
|
|
1025
|
+
the text comes from --dshm-composer-restore-label so nothing is injected into
|
|
1026
|
+
the React-managed card. Opt-in, off by default. */
|
|
1027
|
+
[data-dsh-mobile-composer-collapsed="true"] [data-composer-card] :is([contenteditable="true"],[contenteditable="plaintext-only"],[contenteditable=""],textarea){display:none!important}
|
|
1028
|
+
[data-dsh-mobile-composer-collapsed="true"] [data-composer-card]{
|
|
1029
|
+
padding-top:6px!important;padding-bottom:6px!important;min-height:0!important;cursor:pointer}
|
|
1030
|
+
[data-dsh-mobile-composer-collapsed="true"] [data-composer-card]::before{
|
|
1031
|
+
content:var(--dshm-composer-restore-label, "");display:flex;align-items:center;
|
|
1032
|
+
justify-content:center;min-height:44px;font-size:13px;opacity:.75;pointer-events:none}
|
|
742
1033
|
`;
|
|
743
1034
|
function MobileAppFrame(props) {
|
|
744
1035
|
const state = (0, react.useSyncExternalStore)(props.controller.subscribe, props.controller.getSnapshot);
|
|
@@ -819,6 +1110,34 @@ body:has(.dshm-shell) :is(input,textarea,[contenteditable=true],[contenteditable
|
|
|
819
1110
|
prefetchDispose.current = void 0;
|
|
820
1111
|
};
|
|
821
1112
|
}, [activeSessionId, prefetchEnabled]);
|
|
1113
|
+
const collapseEnabled = (0, react.useSyncExternalStore)(subscribeComposerCollapseEnabled, readComposerCollapseEnabled, () => false);
|
|
1114
|
+
const activeRunning = props.useSessions(selectActiveSessionRunning);
|
|
1115
|
+
const runningRef = (0, react.useRef)(activeRunning);
|
|
1116
|
+
runningRef.current = activeRunning;
|
|
1117
|
+
const active = collapseEnabled && hasSession;
|
|
1118
|
+
const readingMode = (0, react.useRef)(void 0);
|
|
1119
|
+
(0, react.useEffect)(() => {
|
|
1120
|
+
if (!active) return;
|
|
1121
|
+
const installed = installComposerReadingMode({
|
|
1122
|
+
restoreLabel: messages.composerRestore,
|
|
1123
|
+
isRunning: () => runningRef.current
|
|
1124
|
+
});
|
|
1125
|
+
readingMode.current = installed;
|
|
1126
|
+
if (runningRef.current) installed.expand();
|
|
1127
|
+
return () => {
|
|
1128
|
+
readingMode.current = void 0;
|
|
1129
|
+
installed.dispose();
|
|
1130
|
+
};
|
|
1131
|
+
}, [
|
|
1132
|
+
active,
|
|
1133
|
+
activeSessionId,
|
|
1134
|
+
messages.composerRestore
|
|
1135
|
+
]);
|
|
1136
|
+
(0, react.useEffect)(() => {
|
|
1137
|
+
if (!activeRunning) return;
|
|
1138
|
+
readingMode.current?.expand();
|
|
1139
|
+
document.documentElement.setAttribute("data-dsh-mobile-composer-collapsed", "false");
|
|
1140
|
+
}, [activeRunning]);
|
|
822
1141
|
(0, react.useEffect)(() => {
|
|
823
1142
|
const observer = new MutationObserver(() => {
|
|
824
1143
|
setDocumentLanguage(document.documentElement.lang);
|
|
@@ -1102,6 +1421,7 @@ body:has(.dshm-shell) :is(input,textarea,[contenteditable=true],[contenteditable
|
|
|
1102
1421
|
exports.apply = apply;
|
|
1103
1422
|
exports.closeDetailsFromScrim = closeDetailsFromScrim;
|
|
1104
1423
|
exports.inject = inject;
|
|
1424
|
+
exports.installComposerReadingMode = installComposerReadingMode;
|
|
1105
1425
|
exports.installComposerSendKeyboardPolicy = installComposerSendKeyboardPolicy;
|
|
1106
1426
|
exports.isComposerActionTap = isComposerActionTap;
|
|
1107
1427
|
exports.isComposerOwnedFocus = isComposerOwnedFocus;
|
|
@@ -1109,10 +1429,12 @@ body:has(.dshm-shell) :is(input,textarea,[contenteditable=true],[contenteditable
|
|
|
1109
1429
|
exports.isSessionRowNavigation = isSessionRowNavigation;
|
|
1110
1430
|
exports.isSidebarRightControl = isSidebarRightControl;
|
|
1111
1431
|
exports.isWideViewportLayout = isWideViewportLayout;
|
|
1432
|
+
exports.readComposerCollapseEnabled = readComposerCollapseEnabled;
|
|
1112
1433
|
exports.resolveComposerImePolicy = resolveComposerImePolicy;
|
|
1113
1434
|
exports.resolveDeviceShortSide = resolveDeviceShortSide;
|
|
1114
1435
|
exports.resolveMobileLayoutLanguage = resolveMobileLayoutLanguage;
|
|
1115
1436
|
exports.resolveMobileRightbarLayout = resolveMobileRightbarLayout;
|
|
1437
|
+
exports.selectActiveSessionRunning = selectActiveSessionRunning;
|
|
1116
1438
|
return module.exports;
|
|
1117
1439
|
}
|
|
1118
1440
|
});
|