dsh-mobile 0.1.0-alpha.2 → 0.1.0-alpha.21

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/lib/client.js CHANGED
@@ -5,81 +5,462 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
- //#region src/client.ts
9
- const BASE_STYLES = `
10
- @media (max-width: 720px) {
11
- :root {
12
- --dsh-mobile-accent: #2563eb;
13
- --dsh-mobile-font-scale: 1;
14
- --dsh-mobile-radius: 14px;
15
- }
16
- html { font-size: calc(16px * var(--dsh-mobile-font-scale)); }
17
- body { overscroll-behavior: none; }
18
- button, [role='button'], input, textarea, select { min-height: 44px; }
19
- input, textarea, select { font-size: 16px !important; }
20
- [data-sidebar-collapsed] {
21
- padding-top: env(safe-area-inset-top);
22
- padding-right: env(safe-area-inset-right);
23
- padding-bottom: env(safe-area-inset-bottom);
24
- padding-left: env(safe-area-inset-left);
25
- }
26
- [data-shell-overlay] { inset: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); }
27
- * { -webkit-tap-highlight-color: transparent; }
28
- }
29
- @media (prefers-reduced-motion: reduce) {
30
- *, *::before, *::after { scroll-behavior: auto !important; animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
31
- }
32
- .dsh-mobile-control {
33
- position: fixed;
34
- left: 68px;
35
- bottom: 12px;
36
- z-index: 90;
37
- color: #172554;
38
- font: 14px/1.45 system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
39
- }
40
- .dsh-mobile-control button {
41
- min-width: 44px;
42
- min-height: 44px;
43
- border: 1px solid #bfdbfe;
44
- border-radius: 12px;
45
- background: #fff;
46
- color: #1e3a8a;
47
- cursor: pointer;
8
+ //#region src/native-mobile.ts
9
+ /** Mobile feature and compatibility rules applied to DSH React surfaces. */
10
+ const NATIVE_MOBILE_STYLES = `
11
+ @media (max-width:720px) {
12
+ html.dsh-native-mobile-active,html.dsh-native-mobile-active body { width:100%; height:100%; overflow:hidden; }
13
+ html.dsh-native-mobile-active { --dsh-mobile-motion-duration:200ms; --dsh-mobile-motion-ease:cubic-bezier(.22,1,.36,1); }
14
+ html.dsh-native-mobile-active :is(a,button,[role="button"],[role="tab"],[tabindex]) { -webkit-tap-highlight-color:transparent; }
15
+ html.dsh-native-mobile-active[data-dsh-mobile-input="touch"] :is(a,button,[role="button"],[role="tab"],[tabindex]):focus { outline:none !important; }
16
+ [data-dsh-mobile-frame] { grid-template-columns:0 minmax(0,1fr) 0 !important; width:100% !important; height:100dvh !important; overflow:hidden !important; }
17
+ [data-dsh-mobile-center] { grid-column:2 !important; width:100vw !important; min-width:0 !important; }
18
+ [data-dsh-mobile-center] > * { min-width:0 !important; }
19
+ [data-dsh-mobile-header] { min-width:0; padding-right:12px !important; padding-left:50px !important; }
20
+ [data-dsh-mobile-header] [class*="_titleRow"] { box-sizing:border-box !important; min-width:0; min-height:52px !important; height:52px !important; gap:6px !important; padding:4px 8px !important; }
21
+ [data-dsh-mobile-header] [class*="_titleCluster"] { min-width:0; }
22
+ [data-dsh-mobile-header] [class*="_crumbs"] { min-width:0; overflow:hidden; }
23
+ [data-dsh-mobile-header] [class*="_crumb"] { max-width:46vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
24
+ [data-dsh-mobile-header] [class*="_headerActions"] { min-width:0; overflow-x:auto; scrollbar-width:none; }
25
+ [data-dsh-mobile-header] [class*="_headerActions"]::-webkit-scrollbar { display:none; }
26
+ [data-dsh-mobile-header] [class*="_headerUtilities"] { gap:2px !important; }
27
+ [data-dsh-mobile-header] [class*="_sessionLogButton"] { width:40px; min-width:40px; padding:0 !important; overflow:hidden; color:transparent; font-size:0 !important; }
28
+ [data-dsh-mobile-header] [class*="_sessionLogButton"] > * { display:none !important; }
29
+ [data-dsh-mobile-header] [class*="_sessionLogButton"]::after { color:var(--dsw-text, #171a21); content:"日志"; font-size:11px; font-weight:600; }
30
+ [data-dsh-mobile-header] [class*="_tabs"] { min-height:42px; overflow-x:auto; scrollbar-width:none; }
31
+ [data-dsh-mobile-header] [class*="_tabs"]::-webkit-scrollbar { display:none; }
32
+ [data-dsh-mobile-sidebar] { position:fixed !important; z-index:240 !important; inset:0 auto 0 0 !important; width:0 !important; overflow:visible !important; }
33
+ [data-dsh-mobile-sidebar-root] { position:fixed !important; z-index:241 !important; inset:0 auto 0 0 !important; height:100dvh !important; transition:transform var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease) !important; }
34
+ [data-dsh-mobile-sidebar][data-open="true"] [data-dsh-mobile-sidebar-root] { width:min(88vw,340px) !important; box-shadow:18px 0 46px rgb(15 23 42 / 18%); }
35
+ [data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-sidebar-root] { width:0 !important; border:0 !important; background:transparent !important; box-shadow:none !important; overflow:visible !important; }
36
+ [data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-sidebar-root] > :not(:has([data-dsh-mobile-toggle])) { display:none !important; }
37
+ [data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-sidebar-root] > :has([data-dsh-mobile-toggle]) { position:fixed !important; z-index:244 !important; top:env(safe-area-inset-top) !important; left:0 !important; width:50px !important; height:52px !important; padding:4px !important; border:0 !important; background:transparent !important; }
38
+ [data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-sidebar-root] > :has([data-dsh-mobile-toggle]) > :not([data-dsh-mobile-toggle]) { display:none !important; }
39
+ [data-dsh-mobile-toggle] { width:44px !important; height:44px !important; min-width:44px !important; min-height:44px !important; }
40
+ [data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-toggle] svg { display:none !important; }
41
+ [data-dsh-mobile-sidebar][data-open="false"] [data-dsh-mobile-toggle]::after { width:20px; height:2px; border-radius:2px; background:currentColor; box-shadow:0 -6px 0 currentColor,0 6px 0 currentColor; content:""; }
42
+ .dsh-native-mobile-backdrop { position:fixed; z-index:235; inset:0; border:0; background:rgb(15 23 42 / 32%); }
43
+ .dsh-native-mobile-backdrop:not([hidden]) { animation:dsh-mobile-fade-in var(--dsh-mobile-motion-duration) ease-out; }
44
+ .dsh-native-mobile-backdrop[hidden] { display:none; }
45
+ [data-dsh-mobile-details] { position:fixed !important; z-index:250 !important; inset:0 0 0 auto !important; width:min(94vw,460px) !important; max-width:none !important; transform:translateX(100%); transition:transform var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); background:var(--dsw-bg, #fff); box-shadow:-18px 0 46px rgb(15 23 42 / 18%); }
46
+ [data-dsh-mobile-details][data-open="true"] { transform:translateX(0); }
47
+ [data-dsh-mobile-handle] { display:none !important; }
48
+ [data-dsh-mobile-settings] { flex-direction:column !important; width:100vw !important; height:100dvh !important; max-width:none !important; border-radius:0 !important; animation:dsh-mobile-panel-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }
49
+ [data-dsh-mobile-settings-nav] { flex:none !important; width:100% !important; padding:max(14px,env(safe-area-inset-top)) 12px 8px !important; gap:10px !important; border-bottom:1px solid var(--dsw-alias-border-subtle,#e8ebef); }
50
+ [data-dsh-mobile-settings-nav] [class*="_navTitle"] { padding:0 8px !important; font-size:18px !important; line-height:28px !important; }
51
+ [data-dsh-mobile-settings-list] { flex-direction:row !important; gap:4px !important; overflow-x:auto !important; scrollbar-width:none; }
52
+ [data-dsh-mobile-settings-list]::-webkit-scrollbar { display:none; }
53
+ [data-dsh-mobile-settings-list] [class*="_navCell"] { flex:0 0 auto !important; min-width:max-content !important; height:44px !important; padding:10px 12px !important; }
54
+ [data-dsh-mobile-settings-list] [aria-current="true"] { border-color:transparent !important; outline:0 !important; box-shadow:none !important; }
55
+ [data-dsh-mobile-settings-content] { flex:1 1 auto !important; width:100% !important; min-height:0 !important; }
56
+ [data-dsh-mobile-settings-header] { height:48px !important; min-height:48px !important; padding:10px 12px 6px !important; }
57
+ [data-dsh-mobile-settings-header] [class*="_close"] { width:36px !important; height:36px !important; }
58
+ [data-dsh-mobile-settings-options] { box-sizing:border-box !important; width:100% !important; padding:4px 16px max(24px,env(safe-area-inset-bottom)) !important; overflow-x:hidden !important; }
59
+ [data-dsh-mobile-settings-options] > * { width:100% !important; min-width:0 !important; }
60
+ [data-dsh-mobile-settings-options] [data-slot="settings.general.item"] > [class*="_row"] { flex-direction:column !important; align-items:stretch !important; gap:12px !important; }
61
+ [data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_rowText"] { width:100% !important; padding-right:0 !important; }
62
+ [data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_selector"] { box-sizing:border-box !important; align-self:flex-start !important; justify-content:space-between !important; min-width:0 !important; min-height:44px !important; max-width:100% !important; }
63
+ [data-dsh-mobile-settings-options] :is(input,select,textarea,button) { max-width:100%; }
64
+ [data-dsh-mobile-settings-options] :is(input,select,textarea) { box-sizing:border-box; width:100%; min-width:0; }
65
+ [data-dsh-mobile-settings-options] [class*="_head"] { min-width:0; flex-wrap:wrap; }
66
+ [data-dsh-mobile-settings-content][data-dsh-mobile-view-transition="true"],
67
+ [data-dsh-mobile-view][data-dsh-mobile-view-transition="true"] { animation:dsh-mobile-view-in var(--dsh-mobile-motion-duration) var(--dsh-mobile-motion-ease); }
68
+ [data-dsh-mobile-center] textarea { font-size:16px !important; }
69
+ [data-dsh-mobile-center] table { display:block; max-width:100%; overflow-x:auto; }
70
+ [data-dsh-mobile-center] pre { max-width:100%; overflow-x:auto; }
71
+ [data-dsh-mobile-center] :is(img,video,canvas,svg) { max-width:100%; }
72
+ [data-dsh-mobile-message-scroll] { box-sizing:border-box !important; width:100% !important; padding:12px 16px 20px !important; }
73
+ [data-dsh-mobile-message-column] { box-sizing:border-box !important; width:100% !important; max-width:none !important; margin:0 !important; padding:0 !important; gap:14px !important; }
74
+ [data-dsh-mobile-message-column] > * { width:100% !important; max-width:100% !important; }
75
+ [data-dsh-mobile-message-column] [data-disclosure-row] { box-sizing:border-box !important; display:grid !important; grid-template-columns:16px minmax(0,1fr) !important; grid-auto-rows:auto !important; align-items:center !important; column-gap:6px !important; width:100% !important; height:auto !important; min-height:44px !important; padding:5px 0 !important; }
76
+ [data-dsh-mobile-message-column] [data-disclosure-row] > [class*="_leading"] { grid-column:1 !important; grid-row:1 !important; margin-right:0 !important; }
77
+ [data-dsh-mobile-message-column] [data-disclosure-row] > [class*="_title"] { grid-column:2 !important; grid-row:1 !important; min-width:0 !important; overflow:hidden !important; text-overflow:ellipsis !important; white-space:nowrap !important; }
78
+ [data-dsh-mobile-message-column] [data-disclosure-row] > :is([class*="_sep"],[class*="_separator"]) { display:none !important; }
79
+ [data-dsh-mobile-message-column] [data-disclosure-row] > :is([class*="_summary"],[class*="_fileLink"]) { grid-column:2 !important; grid-row:2 !important; width:100% !important; min-width:0 !important; max-width:100% !important; overflow:hidden !important; line-height:20px !important; text-overflow:ellipsis !important; white-space:nowrap !important; }
80
+ [data-dsh-mobile-message-column] [data-disclosure-row] > [class*="_summarySuffix"] { grid-column:2 !important; grid-row:3 !important; margin-left:0 !important; }
81
+ [data-dsh-mobile-message-column] [data-context-fields] > * { display:grid !important; grid-template-columns:minmax(72px,30%) minmax(0,1fr) !important; gap:4px 10px !important; }
82
+ [data-dsh-mobile-message-column] [class*="_ioSection"] { grid-template-columns:1fr !important; row-gap:4px !important; }
83
+ [data-dsh-mobile-message-column] [class*="_body"] { max-width:100% !important; overflow-wrap:anywhere; }
84
+ [data-dsh-mobile-center] [class*="_composer"] { padding-left:8px !important; padding-right:8px !important; padding-bottom:max(8px,env(safe-area-inset-bottom)) !important; }
85
+ [data-dsh-mobile-center] [class*="_card"]:has(textarea) [class*="_row"] { display:flex !important; align-items:center !important; gap:4px !important; }
86
+ [data-dsh-mobile-center] [class*="_card"]:has(textarea) [class*="_tools"] { flex:0 0 auto !important; width:auto !important; min-width:0 !important; gap:6px !important; }
87
+ [data-dsh-mobile-center] [class*="_card"]:has(textarea) [class*="_trailing"] { flex:1 1 auto !important; min-width:0 !important; gap:6px !important; }
88
+ [data-dsh-mobile-center] [class*="_card"]:has(textarea) [class*="_trailing"] [class*="_root"]:has(button[aria-label^="选择模型"]) { flex:1 1 auto !important; min-width:0 !important; }
89
+ [data-dsh-mobile-center] [class*="_card"]:has(textarea) button[aria-label^="选择模型"] { width:100% !important; min-width:0 !important; padding-left:6px !important; padding-right:4px !important; }
90
+ [data-dsh-mobile-center] [class*="_card"]:has(textarea) button[aria-label^="选择模型"] [class*="_triggerLabel"] { max-width:78px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
91
+ [data-dsh-mobile-center] [class*="_root"]:has(> [class*="_card"] textarea) { box-sizing:border-box !important; width:100% !important; padding:0 0 8px !important; }
92
+ [data-dsh-mobile-center] [class*="_root"]:has(> [class*="_card"] textarea) > [class=""]:last-child { display:none !important; }
93
+ .dsh-native-mobile-attach { display:inline-flex; align-items:center; justify-content:center; flex:0 0 44px; width:44px; height:44px; padding:0; border:0; border-radius:12px; background:transparent; color:var(--dsw-text-secondary,#5e6673); cursor:pointer; }
94
+ .dsh-native-mobile-attach:active { background:var(--dsw-bg-subtle,#f1f3f6); }
95
+ .dsh-native-mobile-attach:focus-visible { outline:2px solid var(--dsw-accent,#4c7eea); outline-offset:2px; }
96
+ .dsh-native-mobile-attach:disabled { cursor:not-allowed; opacity:.38; }
97
+ .dsh-native-mobile-attach svg { width:22px; height:22px; }
98
+ .dsh-native-mobile-sheet-mask { position:fixed; z-index:1080; inset:0; border:0; background:rgb(15 23 42 / 32%); }
99
+ .dsh-native-mobile-sheet-mask[hidden] { display:none; }
100
+ .dsh-native-mobile-sheet { position:fixed; z-index:1081; right:0; bottom:0; left:0; display:flex; flex-direction:column; gap:8px; max-height:min(78dvh,680px); padding:8px 16px max(18px,env(safe-area-inset-bottom)); overflow-y:auto; border-radius:22px 22px 0 0; background:var(--dsw-alias-bg-layer-2,#fff); box-shadow:0 -16px 44px rgb(15 23 42 / 16%); animation:dsh-mobile-sheet-in 220ms var(--dsh-mobile-motion-ease); }
101
+ .dsh-native-mobile-sheet[hidden] { display:none; }
102
+ .dsh-native-mobile-sheet__header { position:sticky; z-index:1; top:-8px; display:flex; align-items:center; justify-content:space-between; min-height:48px; padding:8px 0; background:var(--dsw-alias-bg-layer-2,#fff); }
103
+ .dsh-native-mobile-sheet__header strong { font-size:17px; font-weight:600; }
104
+ .dsh-native-mobile-sheet__close { width:44px; height:44px; border:0; border-radius:12px; background:transparent; color:inherit; font-size:24px; }
105
+ .dsh-native-mobile-sheet__path { margin:0 0 4px; overflow-wrap:anywhere; color:var(--dsw-alias-label-secondary,#6b7280); font-size:12px; }
106
+ .dsh-native-mobile-sheet__choice { display:flex; flex-direction:column; align-items:flex-start; gap:2px; width:100%; min-height:56px; padding:10px 14px; border:1px solid var(--dsw-alias-border-subtle,#e2e6eb); border-radius:14px; background:var(--dsw-alias-bg-layer-2,#fff); color:inherit; text-align:left; }
107
+ .dsh-native-mobile-sheet__choice:active { background:var(--dsw-alias-interactive-bg-hover,#f1f3f6); }
108
+ .dsh-native-mobile-sheet__choice small { color:var(--dsw-alias-label-secondary,#6b7280); }
109
+ .dsh-native-mobile-sheet__error { margin:4px 0; color:var(--dsw-alias-status-error,#b42318); font-size:13px; }
48
110
  }
49
- .dsh-mobile-control__trigger {
50
- width: 100%;
51
- min-width: 44px;
52
- min-height: 44px;
53
- padding: 0 12px;
54
- border: 0;
55
- border-radius: 10px;
56
- background: transparent;
57
- color: inherit;
58
- font: inherit;
59
- font-weight: 700;
60
- cursor: pointer;
111
+ @keyframes dsh-mobile-fade-in { from { opacity:0; } }
112
+ @keyframes dsh-mobile-panel-in { from { opacity:.72; transform:translateY(6px); } }
113
+ @keyframes dsh-mobile-view-in { from { opacity:.58; transform:translateY(5px); } }
114
+ @keyframes dsh-mobile-sheet-in { from { opacity:.7; transform:translateY(18px); } }
115
+ @media (max-width:420px) {
116
+ [data-dsh-mobile-header] [class*="_headerActions"] { max-width:42vw; }
117
+ [data-dsh-mobile-header] [class*="_titleRow"] { min-height:50px; }
118
+ [data-dsh-mobile-settings-options] [data-slot="settings.general.item"] [class*="_selector"] { align-self:stretch !important; width:100% !important; }
119
+ [data-dsh-mobile-message-column] [data-context-fields] > * { grid-template-columns:1fr !important; }
61
120
  }
62
- .dsh-mobile-control__trigger:hover { background: rgb(37 99 235 / 8%); }
63
- .dsh-mobile-control__panel {
64
- width: min(320px, calc(100vw - 24px));
65
- margin-bottom: 8px;
66
- padding: 16px;
67
- border: 1px solid #dbeafe;
68
- border-radius: 16px;
69
- background: #fff;
70
- box-shadow: 0 10px 30px rgb(15 23 42 / 12%);
121
+ @media (prefers-reduced-motion:reduce) {
122
+ [data-dsh-mobile-sidebar-root],[data-dsh-mobile-details] { transition:none !important; }
123
+ .dsh-native-mobile-backdrop:not([hidden]),[data-dsh-mobile-settings],
124
+ [data-dsh-mobile-settings-content][data-dsh-mobile-view-transition="true"],
125
+ [data-dsh-mobile-view][data-dsh-mobile-view-transition="true"],
126
+ .dsh-native-mobile-sheet { animation:none !important; }
71
127
  }
72
- .dsh-mobile-control__title { margin: 0 0 4px; font-size: 16px; font-weight: 800; }
73
- .dsh-mobile-control__status { margin: 0 0 14px; color: #475569; overflow-wrap: anywhere; }
74
- .dsh-mobile-control__actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
75
- .dsh-mobile-control__actions button[data-primary='true'] { background: #2563eb; border-color: #2563eb; color: #fff; }
76
- .dsh-mobile-control[hidden], .dsh-mobile-control__panel[hidden] { display: none; }
77
128
  `;
129
+ function classToken(element, suffix) {
130
+ return Array.from(element.classList).some((value) => value.endsWith(suffix));
131
+ }
132
+ function firstByClassSuffix(root, suffix) {
133
+ return Array.from(root.querySelectorAll("[class]")).find((element) => classToken(element, suffix));
134
+ }
135
+ function composerCard(textarea) {
136
+ let candidate = textarea.parentElement;
137
+ while (candidate !== null && !classToken(candidate, "_card")) candidate = candidate.parentElement;
138
+ return candidate ?? void 0;
139
+ }
140
+ function paperclipIcon() {
141
+ const namespace = "http://www.w3.org/2000/svg";
142
+ const svg = document.createElementNS(namespace, "svg");
143
+ svg.setAttribute("viewBox", "0 0 24 24");
144
+ svg.setAttribute("fill", "none");
145
+ svg.setAttribute("stroke", "currentColor");
146
+ svg.setAttribute("stroke-width", "2");
147
+ svg.setAttribute("stroke-linecap", "round");
148
+ svg.setAttribute("stroke-linejoin", "round");
149
+ svg.setAttribute("aria-hidden", "true");
150
+ const path = document.createElementNS(namespace, "path");
151
+ path.setAttribute("d", "m21.4 11.6-8.9 8.9a6 6 0 0 1-8.5-8.5l9.6-9.6a4 4 0 0 1 5.7 5.7l-9.7 9.6a2 2 0 0 1-2.8-2.8l8.9-8.9");
152
+ svg.append(path);
153
+ return svg;
154
+ }
155
+ /** Add mobile semantics and phone-local file actions without replacing feature trees. */
156
+ function installNativeMobileSurface() {
157
+ document.documentElement.classList.add("dsh-native-mobile-active");
158
+ const setInputMode = (mode) => {
159
+ document.documentElement.dataset.dshMobileInput = mode;
160
+ };
161
+ const onPointerDown = (event) => {
162
+ if (event.pointerType === "touch" || event.pointerType === "pen") setInputMode("touch");
163
+ };
164
+ const onKeyDown = (event) => {
165
+ if (event.key === "Tab" || event.key.startsWith("Arrow")) setInputMode("keyboard");
166
+ };
167
+ document.addEventListener("pointerdown", onPointerDown, true);
168
+ document.addEventListener("keydown", onKeyDown, true);
169
+ const backdrop = document.createElement("button");
170
+ backdrop.type = "button";
171
+ backdrop.className = "dsh-native-mobile-backdrop";
172
+ backdrop.hidden = true;
173
+ backdrop.setAttribute("aria-label", "关闭工作区导航");
174
+ document.body.append(backdrop);
175
+ let frame;
176
+ let sidebar;
177
+ let sidebarRoot;
178
+ let toggle;
179
+ let viewArea;
180
+ let scheduled = 0;
181
+ let transitionFrame = 0;
182
+ let transitionRestartFrame = 0;
183
+ let transitionTimer = 0;
184
+ let transitionTarget;
185
+ const fileInput = document.createElement("input");
186
+ fileInput.type = "file";
187
+ fileInput.accept = "image/*";
188
+ fileInput.multiple = true;
189
+ fileInput.hidden = true;
190
+ fileInput.setAttribute("aria-hidden", "true");
191
+ const attach = document.createElement("button");
192
+ attach.type = "button";
193
+ attach.className = "dsh-native-mobile-attach";
194
+ attach.setAttribute("aria-label", "添加图片");
195
+ attach.setAttribute("title", "添加图片");
196
+ attach.append(paperclipIcon());
197
+ document.body.append(fileInput);
198
+ const sheetMask = document.createElement("button");
199
+ sheetMask.type = "button";
200
+ sheetMask.className = "dsh-native-mobile-sheet-mask";
201
+ sheetMask.hidden = true;
202
+ sheetMask.setAttribute("aria-label", "关闭图片选择");
203
+ const sheet = document.createElement("section");
204
+ sheet.className = "dsh-native-mobile-sheet";
205
+ sheet.hidden = true;
206
+ sheet.setAttribute("aria-label", "添加图片");
207
+ document.body.append(sheetMask, sheet);
208
+ const handFilesToComposer = (files) => {
209
+ const textarea = document.querySelector("[data-dsh-mobile-center] textarea");
210
+ if (textarea === null || files.length === 0) return;
211
+ const paste = new Event("paste", {
212
+ bubbles: true,
213
+ cancelable: true
214
+ });
215
+ Object.defineProperty(paste, "clipboardData", { value: Object.freeze({
216
+ files,
217
+ items: Object.freeze(files.map((file) => Object.freeze({
218
+ kind: "file",
219
+ type: file.type,
220
+ getAsFile: () => file
221
+ }))),
222
+ getData: () => ""
223
+ }) });
224
+ textarea.dispatchEvent(paste);
225
+ };
226
+ const closeSheet = () => {
227
+ sheet.hidden = true;
228
+ sheetMask.hidden = true;
229
+ sheet.replaceChildren();
230
+ };
231
+ const sheetHeader = (title) => {
232
+ const header = document.createElement("header");
233
+ header.className = "dsh-native-mobile-sheet__header";
234
+ const heading = document.createElement("strong");
235
+ heading.textContent = title;
236
+ const close = document.createElement("button");
237
+ close.type = "button";
238
+ close.className = "dsh-native-mobile-sheet__close";
239
+ close.setAttribute("aria-label", "关闭");
240
+ close.textContent = "×";
241
+ close.addEventListener("click", closeSheet);
242
+ header.append(heading, close);
243
+ sheet.append(header);
244
+ };
245
+ const choice = (title, detail, action) => {
246
+ const button = document.createElement("button");
247
+ button.type = "button";
248
+ button.className = "dsh-native-mobile-sheet__choice";
249
+ const label = document.createElement("span");
250
+ label.textContent = title;
251
+ const hint = document.createElement("small");
252
+ hint.textContent = detail;
253
+ button.append(label, hint);
254
+ button.addEventListener("click", action);
255
+ return button;
256
+ };
257
+ const showSheet = () => {
258
+ sheet.hidden = false;
259
+ sheetMask.hidden = false;
260
+ };
261
+ const openComputerImages = async (path) => {
262
+ sheet.replaceChildren();
263
+ sheetHeader("电脑图片");
264
+ const progress = document.createElement("p");
265
+ progress.className = "dsh-native-mobile-sheet__path";
266
+ progress.textContent = "正在读取电脑目录…";
267
+ sheet.append(progress);
268
+ showSheet();
269
+ try {
270
+ const suffix = path === void 0 ? "" : `?path=${encodeURIComponent(path)}`;
271
+ const response = await fetch(`/mobile-access/computer-images${suffix}`, {
272
+ credentials: "same-origin",
273
+ cache: "no-store"
274
+ });
275
+ if (!response.ok) throw new Error(`读取失败(${String(response.status)})`);
276
+ const listing = await response.json();
277
+ progress.textContent = listing.path;
278
+ if (listing.parent !== void 0) sheet.append(choice("返回上一级", listing.parent, () => {
279
+ openComputerImages(listing.parent);
280
+ }));
281
+ for (const entry of listing.entries) {
282
+ if (entry.kind === "directory") {
283
+ sheet.append(choice(entry.name, "文件夹", () => {
284
+ openComputerImages(entry.path);
285
+ }));
286
+ continue;
287
+ }
288
+ const row = choice(entry.name, "添加这张电脑图片", () => {
289
+ row.disabled = true;
290
+ fetch(`/mobile-access/computer-image?path=${encodeURIComponent(entry.path)}`, {
291
+ credentials: "same-origin",
292
+ cache: "no-store"
293
+ }).then(async (response) => {
294
+ if (!response.ok) throw new Error(`读取失败(${String(response.status)})`);
295
+ const blob = await response.blob();
296
+ handFilesToComposer([new File([blob], entry.name, { type: blob.type })]);
297
+ closeSheet();
298
+ }).catch((error) => {
299
+ row.disabled = false;
300
+ progress.className = "dsh-native-mobile-sheet__error";
301
+ progress.textContent = error instanceof Error ? error.message : String(error);
302
+ });
303
+ });
304
+ sheet.append(row);
305
+ }
306
+ } catch (error) {
307
+ progress.className = "dsh-native-mobile-sheet__error";
308
+ progress.textContent = error instanceof Error ? error.message : String(error);
309
+ }
310
+ };
311
+ const openAttachmentSources = () => {
312
+ sheet.replaceChildren();
313
+ sheetHeader("添加图片");
314
+ sheet.append(choice("从手机选择", "相册、相机或手机存储", () => {
315
+ closeSheet();
316
+ fileInput.click();
317
+ }), choice("从电脑选择", "在手机页面浏览电脑中的图片", () => {
318
+ openComputerImages();
319
+ }));
320
+ showSheet();
321
+ };
322
+ attach.addEventListener("click", openAttachmentSources);
323
+ sheetMask.addEventListener("click", closeSheet);
324
+ fileInput.addEventListener("change", () => {
325
+ handFilesToComposer([...fileInput.files ?? []]);
326
+ fileInput.value = "";
327
+ });
328
+ const animateNavigation = (event) => {
329
+ if (!(event.target instanceof Element)) return;
330
+ const trigger = event.target.closest("button,a,[role=\"tab\"],[aria-selected]");
331
+ if (trigger === null || trigger.hasAttribute("disabled") || trigger.getAttribute("aria-disabled") === "true") return;
332
+ if (trigger.getAttribute("aria-selected") === "true" || trigger.getAttribute("aria-current") === "true") return;
333
+ const settingsNavigation = trigger.closest("[data-dsh-mobile-settings-list]") !== null;
334
+ const conversationNavigation = trigger.matches("[role=\"tab\"]");
335
+ const sidebarNavigation = trigger.closest("[data-dsh-mobile-sidebar-root]") !== null && trigger.closest("[data-dsh-mobile-toggle]") === null;
336
+ if (!settingsNavigation && !conversationNavigation && !sidebarNavigation) return;
337
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
338
+ if (transitionFrame !== 0) cancelAnimationFrame(transitionFrame);
339
+ if (transitionRestartFrame !== 0) cancelAnimationFrame(transitionRestartFrame);
340
+ transitionFrame = requestAnimationFrame(() => {
341
+ transitionFrame = 0;
342
+ const target = settingsNavigation ? document.querySelector("[data-dsh-mobile-settings-content]") : viewArea;
343
+ if (target === null || target === void 0) return;
344
+ transitionTarget?.removeAttribute("data-dsh-mobile-view-transition");
345
+ target.removeAttribute("data-dsh-mobile-view-transition");
346
+ transitionRestartFrame = requestAnimationFrame(() => {
347
+ transitionRestartFrame = 0;
348
+ transitionTarget = target;
349
+ target.dataset.dshMobileViewTransition = "true";
350
+ if (transitionTimer !== 0) clearTimeout(transitionTimer);
351
+ transitionTimer = window.setTimeout(() => {
352
+ target.removeAttribute("data-dsh-mobile-view-transition");
353
+ if (transitionTarget === target) transitionTarget = void 0;
354
+ transitionTimer = 0;
355
+ }, 240);
356
+ });
357
+ });
358
+ };
359
+ document.addEventListener("click", animateNavigation);
360
+ const sync = () => {
361
+ scheduled = 0;
362
+ frame = firstByClassSuffix(document, "_frame");
363
+ const dedicatedCenter = document.querySelector(".dshm-main") ?? void 0;
364
+ if (frame !== void 0) frame.dataset.dshMobileFrame = "true";
365
+ sidebar = frame === void 0 ? void 0 : firstByClassSuffix(frame, "_sidebarCol");
366
+ const center = frame === void 0 ? dedicatedCenter : firstByClassSuffix(frame, "_centerCol");
367
+ const details = frame === void 0 ? void 0 : firstByClassSuffix(frame, "_detailsCol");
368
+ const handle = frame === void 0 ? void 0 : firstByClassSuffix(frame, "_handle");
369
+ if (center === void 0) return;
370
+ if (center !== void 0) {
371
+ center.dataset.dshMobileCenter = "true";
372
+ center.querySelector("header")?.setAttribute("data-dsh-mobile-header", "true");
373
+ viewArea = firstByClassSuffix(center, "_viewArea");
374
+ if (viewArea !== void 0) viewArea.dataset.dshMobileView = "true";
375
+ const conversation = center.querySelector("[data-conversation-scroll]");
376
+ const messageColumn = conversation === null ? void 0 : firstByClassSuffix(conversation, "_column");
377
+ const messageScroll = messageColumn?.parentElement;
378
+ if (messageColumn !== void 0 && messageScroll !== null && messageScroll !== void 0 && classToken(messageScroll, "_scroll")) {
379
+ messageColumn.dataset.dshMobileMessageColumn = "true";
380
+ messageScroll.dataset.dshMobileMessageScroll = "true";
381
+ }
382
+ }
383
+ if (handle !== void 0) handle.dataset.dshMobileHandle = "true";
384
+ if (details !== void 0) {
385
+ details.dataset.dshMobileDetails = "true";
386
+ const lastColumn = frame?.style.gridTemplateColumns.trim().split(/\s+/).at(-1);
387
+ details.dataset.open = String(lastColumn !== void 0 && lastColumn !== "0px" && lastColumn !== "0");
388
+ }
389
+ const settings = Array.from(document.querySelectorAll("[role=\"dialog\"]")).find((dialog) => {
390
+ return Array.from(dialog.children).find((child) => child instanceof HTMLElement && classToken(child, "_nav")) !== void 0;
391
+ });
392
+ if (settings !== void 0) {
393
+ settings.dataset.dshMobileSettings = "true";
394
+ const nav = Array.from(settings.children).find((child) => child instanceof HTMLElement && classToken(child, "_nav"));
395
+ const content = Array.from(settings.children).find((child) => child instanceof HTMLElement && classToken(child, "_content"));
396
+ if (nav !== void 0) {
397
+ nav.dataset.dshMobileSettingsNav = "true";
398
+ firstByClassSuffix(nav, "_navList")?.setAttribute("data-dsh-mobile-settings-list", "true");
399
+ }
400
+ if (content !== void 0) {
401
+ content.dataset.dshMobileSettingsContent = "true";
402
+ firstByClassSuffix(content, "_header")?.setAttribute("data-dsh-mobile-settings-header", "true");
403
+ firstByClassSuffix(content, "_options")?.setAttribute("data-dsh-mobile-settings-options", "true");
404
+ }
405
+ }
406
+ const textarea = center?.querySelector("textarea");
407
+ const card = textarea === null || textarea === void 0 ? void 0 : composerCard(textarea);
408
+ const tools = card === void 0 ? void 0 : firstByClassSuffix(card, "_tools");
409
+ attach.disabled = textarea === void 0 || textarea === null || textarea.disabled || textarea.readOnly;
410
+ if (tools !== void 0 && attach.parentElement !== tools) tools.append(attach);
411
+ if (sidebar === void 0) return;
412
+ sidebar.dataset.dshMobileSidebar = "true";
413
+ toggle = firstByClassSuffix(sidebar, "_toggle");
414
+ let candidate = toggle?.parentElement;
415
+ while (candidate !== void 0 && candidate !== null && candidate !== sidebar && !classToken(candidate, "_root")) candidate = candidate.parentElement;
416
+ sidebarRoot = candidate !== sidebar && candidate !== null ? candidate : void 0;
417
+ if (sidebarRoot === void 0) return;
418
+ sidebarRoot.dataset.dshMobileSidebarRoot = "true";
419
+ if (toggle !== void 0) toggle.dataset.dshMobileToggle = "true";
420
+ const collapsed = classToken(sidebarRoot, "_collapsed");
421
+ sidebar.dataset.open = String(!collapsed);
422
+ backdrop.hidden = collapsed;
423
+ };
424
+ const schedule = () => {
425
+ if (scheduled === 0) scheduled = requestAnimationFrame(sync);
426
+ };
427
+ const observer = new MutationObserver(schedule);
428
+ observer.observe(document.body, {
429
+ childList: true,
430
+ subtree: true,
431
+ attributes: true,
432
+ attributeFilter: ["class", "style"]
433
+ });
434
+ backdrop.addEventListener("click", () => {
435
+ if (sidebar?.dataset.open === "true") toggle?.click();
436
+ });
437
+ sync();
438
+ return () => {
439
+ observer.disconnect();
440
+ if (scheduled !== 0) cancelAnimationFrame(scheduled);
441
+ if (transitionFrame !== 0) cancelAnimationFrame(transitionFrame);
442
+ if (transitionRestartFrame !== 0) cancelAnimationFrame(transitionRestartFrame);
443
+ if (transitionTimer !== 0) clearTimeout(transitionTimer);
444
+ transitionTarget?.removeAttribute("data-dsh-mobile-view-transition");
445
+ document.removeEventListener("pointerdown", onPointerDown, true);
446
+ document.removeEventListener("keydown", onKeyDown, true);
447
+ document.removeEventListener("click", animateNavigation);
448
+ backdrop.remove();
449
+ attach.remove();
450
+ fileInput.remove();
451
+ sheet.remove();
452
+ sheetMask.remove();
453
+ document.documentElement.classList.remove("dsh-native-mobile-active");
454
+ delete document.documentElement.dataset.dshMobileInput;
455
+ };
456
+ }
457
+ //#endregion
458
+ //#region src/client.ts
78
459
  function isLoopbackHost(hostname) {
79
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
460
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
80
461
  }
81
- function element(name, className) {
82
- const node = document.createElement(name);
462
+ function element(tag, className) {
463
+ const node = document.createElement(tag);
83
464
  if (className !== void 0) node.className = className;
84
465
  return node;
85
466
  }
@@ -95,63 +476,64 @@ window.__ModuleLoader__.load({
95
476
  if (!response.ok) throw new Error(typeof body.error === "string" ? body.error : `HTTP ${String(response.status)}`);
96
477
  return body;
97
478
  }
98
- function mobileExtensionRequest(path, init = {}) {
99
- const target = new URL(path, location.href);
100
- if (target.origin !== location.origin) throw new TypeError("mobile extension requests must stay on the DSH origin");
101
- const headers = new Headers(init.headers);
102
- const method = (init.method ?? "GET").toUpperCase();
103
- if (method !== "GET" && method !== "HEAD" && !headers.has("x-dsh-mobile-csrf")) {
104
- const csrf = document.cookie.split(";").map((entry) => entry.trim()).find((entry) => entry.startsWith("dsh_ma_csrf="))?.slice(12);
105
- if (csrf !== void 0) headers.set("x-dsh-mobile-csrf", csrf);
106
- }
107
- return fetch(target, {
108
- ...init,
109
- cache: "no-store",
110
- credentials: "same-origin",
111
- headers,
112
- redirect: "error"
113
- });
114
- }
115
479
  function installControl() {
116
- const zh = navigator.language.toLowerCase().startsWith("zh");
117
480
  const root = element("div", "dsh-mobile-control");
118
481
  const panel = element("section", "dsh-mobile-control__panel");
119
482
  panel.hidden = true;
120
- const title = element("h2", "dsh-mobile-control__title");
121
- title.textContent = zh ? "移动访问" : "Mobile access";
483
+ panel.setAttribute("aria-label", "移动访问");
484
+ const header = element("header", "dsh-mobile-control__header");
485
+ const title = element("h2");
486
+ title.textContent = "移动访问";
487
+ const close = element("button", "dsh-mobile-control__close");
488
+ close.type = "button";
489
+ close.textContent = "×";
490
+ close.setAttribute("aria-label", "收起移动访问");
491
+ const access = element("div", "dsh-mobile-control__access");
492
+ access.hidden = true;
493
+ const accessLabel = element("span", "dsh-mobile-control__access-label");
494
+ accessLabel.textContent = "浏览器访问";
495
+ const accessLink = element("a", "dsh-mobile-control__access-link");
496
+ accessLink.target = "_blank";
497
+ accessLink.rel = "noreferrer";
498
+ access.append(accessLabel, accessLink);
122
499
  const status = element("p", "dsh-mobile-control__status");
123
- status.textContent = zh ? "正在读取状态…" : "Reading status…";
500
+ status.textContent = "正在读取状态…";
124
501
  const actions = element("div", "dsh-mobile-control__actions");
125
- const toggle = element("button");
126
- toggle.dataset.primary = "true";
127
- const pair = element("button");
128
- pair.textContent = zh ? "生成配对链接" : "Create pairing link";
502
+ const toggle = element("button", "dsh-mobile-control__secondary");
503
+ toggle.type = "button";
504
+ const pair = element("button", "dsh-mobile-control__primary");
505
+ pair.type = "button";
506
+ pair.textContent = "生成并复制密钥";
507
+ header.append(title, close);
129
508
  actions.append(toggle, pair);
130
- panel.append(title, status, actions);
509
+ panel.append(header, access, status, actions);
131
510
  root.append(panel);
132
511
  document.body.append(root);
133
512
  let running = false;
513
+ let origin = "";
514
+ const setOpen = (open) => {
515
+ panel.hidden = !open;
516
+ for (const trigger of document.querySelectorAll(".dsh-mobile-control__trigger")) trigger.setAttribute("aria-expanded", String(open));
517
+ };
134
518
  const render = (data) => {
135
519
  running = data.running === true;
136
- const origin = typeof data.origin === "string" ? data.origin : void 0;
137
- status.textContent = running ? zh ? `已开启:${origin ?? ""}` : `Running: ${origin ?? ""}` : zh ? "已关闭。DSH 仍只在本机可用。" : "Stopped. DSH remains local-only.";
138
- toggle.textContent = running ? zh ? "关闭" : "Stop" : zh ? "开启" : "Start";
520
+ origin = running && typeof data.origin === "string" ? data.origin : "";
521
+ access.hidden = origin === "";
522
+ accessLink.href = origin;
523
+ accessLink.textContent = origin;
524
+ accessLink.title = origin;
525
+ status.classList.toggle("is-running", running);
526
+ status.textContent = running ? "移动访问已开启。" : "已关闭。DSH 仍只在本机可用。";
527
+ toggle.textContent = running ? "关闭移动访问" : "开启移动访问";
139
528
  pair.disabled = !running;
140
529
  };
141
- const refresh = async () => {
142
- try {
143
- render(await requestJson("/api/mobile-access/control"));
144
- } catch (error) {
145
- status.textContent = error instanceof Error ? error.message : String(error);
146
- }
147
- };
148
530
  toggle.addEventListener("click", () => {
149
531
  toggle.disabled = true;
150
532
  requestJson("/api/mobile-access/control", {
151
533
  method: "POST",
152
534
  body: JSON.stringify({ running: !running })
153
535
  }).then(render, (error) => {
154
- status.textContent = error instanceof Error ? error.message : String(error);
536
+ status.textContent = String(error);
155
537
  }).finally(() => {
156
538
  toggle.disabled = false;
157
539
  });
@@ -162,154 +544,189 @@ window.__ModuleLoader__.load({
162
544
  method: "POST",
163
545
  body: "{}"
164
546
  }).then(async (data) => {
165
- const url = typeof data.pairUrl === "string" ? data.pairUrl : "";
166
- if (url !== "" && navigator.clipboard !== void 0) await navigator.clipboard.writeText(url);
167
- status.textContent = url === "" ? zh ? "无法生成链接" : "Could not create link" : zh ? `链接已复制:${url}` : `Copied: ${url}`;
547
+ const key = typeof data.appKey === "string" ? data.appKey : "";
548
+ if (key === "") {
549
+ status.textContent = "无法生成配对密钥。";
550
+ return;
551
+ }
552
+ try {
553
+ await navigator.clipboard.writeText(key);
554
+ status.textContent = "配对密钥已复制,请粘贴到 Android App。";
555
+ } catch {
556
+ status.textContent = `请复制配对密钥:${key}`;
557
+ status.classList.add("is-key");
558
+ }
168
559
  }, (error) => {
169
- status.textContent = error instanceof Error ? error.message : String(error);
560
+ status.textContent = String(error);
170
561
  }).finally(() => {
171
562
  pair.disabled = !running;
172
563
  });
173
564
  });
174
- refresh();
565
+ close.addEventListener("click", () => {
566
+ setOpen(false);
567
+ });
568
+ const dismiss = (event) => {
569
+ if (panel.hidden || !(event.target instanceof Node)) return;
570
+ if (!panel.contains(event.target) && !document.querySelector(".dsh-mobile-control__trigger")?.contains(event.target)) setOpen(false);
571
+ };
572
+ document.addEventListener("pointerdown", dismiss);
573
+ requestJson("/api/mobile-access/control").then(render, (error) => {
574
+ status.textContent = String(error);
575
+ });
175
576
  return {
176
577
  remove: () => {
578
+ document.removeEventListener("pointerdown", dismiss);
177
579
  root.remove();
178
580
  },
179
581
  toggle: () => {
180
- panel.hidden = !panel.hidden;
582
+ setOpen(panel.hidden !== false);
181
583
  }
182
584
  };
183
585
  }
184
- function installCustomExtension() {
586
+ function mobileRequest(path, init = {}) {
587
+ const target = new URL(path, location.href);
588
+ if (target.origin !== location.origin) throw new TypeError("mobile extension requests must stay on the DSH origin");
589
+ const headers = new Headers(init.headers);
590
+ const method = (init.method ?? "GET").toUpperCase();
591
+ if (method !== "GET" && method !== "HEAD") {
592
+ const csrf = document.cookie.split(";").map((value) => value.trim()).find((value) => value.startsWith("dsh_ma_csrf="))?.slice(12);
593
+ if (csrf !== void 0) headers.set("x-dsh-mobile-csrf", csrf);
594
+ }
595
+ return fetch(target, {
596
+ ...init,
597
+ headers,
598
+ credentials: "same-origin",
599
+ cache: "no-store",
600
+ redirect: "error"
601
+ });
602
+ }
603
+ function installCustomAssets() {
604
+ const style = element("style");
605
+ style.dataset.plugin = "dsh-mobile-custom";
606
+ document.head.append(style);
607
+ let cssLoading = false;
608
+ const refreshCss = async () => {
609
+ if (cssLoading || document.visibilityState === "hidden") return;
610
+ cssLoading = true;
611
+ try {
612
+ const response = await fetch("/mobile-access/custom.css", {
613
+ cache: "no-store",
614
+ credentials: "same-origin"
615
+ });
616
+ if (response.ok) {
617
+ const css = await response.text();
618
+ if (style.textContent !== css) style.textContent = css;
619
+ }
620
+ } finally {
621
+ cssLoading = false;
622
+ }
623
+ };
185
624
  let activeRoot;
186
- let activeDispose;
187
- let observedSource = "";
188
- let loading = false;
189
- let pendingMount;
190
- const previousRegistry = window.dshMobile;
191
- const registry = Object.freeze({ register: (mount) => {
192
- if (typeof mount !== "function") throw new TypeError("dshMobile.register requires a mount function");
193
- pendingMount = mount;
625
+ let disposeActive;
626
+ let source = "";
627
+ let pending;
628
+ const previous = window.dshMobile;
629
+ window.dshMobile = Object.freeze({ register: (mount) => {
630
+ pending = mount;
194
631
  } });
195
- const registeredMount = () => pendingMount;
196
- window.dshMobile = registry;
197
- const refresh = async () => {
198
- if (loading || document.visibilityState === "hidden") return;
199
- loading = true;
632
+ const registeredMount = () => pending;
633
+ const refreshJs = async () => {
200
634
  try {
201
635
  const response = await fetch("/mobile-access/custom.js", {
202
636
  cache: "no-store",
203
- credentials: "same-origin",
204
- redirect: "error"
637
+ credentials: "same-origin"
205
638
  });
206
- if (!response.ok || !response.headers.get("content-type")?.startsWith("text/javascript")) return;
207
- const source = await response.text();
208
- if (source === observedSource) return;
209
- observedSource = source;
210
- pendingMount = void 0;
211
- const script = document.createElement("script");
212
- script.textContent = `${source}\n//# sourceURL=dsh-mobile-custom.js`;
639
+ if (!response.ok) return;
640
+ const next = await response.text();
641
+ if (next === source) return;
642
+ source = next;
643
+ pending = void 0;
644
+ const script = element("script");
645
+ script.textContent = `${next}\n//# sourceURL=dsh-mobile-custom.js`;
213
646
  document.head.append(script);
214
647
  script.remove();
215
648
  const mount = registeredMount();
216
649
  if (mount === void 0) return;
217
- const nextRoot = document.createElement("div");
650
+ const nextRoot = element("div");
218
651
  nextRoot.dataset.dshMobileExtension = "true";
219
652
  document.body.append(nextRoot);
220
- try {
221
- const nextDispose = mount(Object.freeze({
222
- document,
223
- request: mobileExtensionRequest,
224
- root: nextRoot,
225
- window
226
- }));
227
- if (nextDispose !== void 0 && typeof nextDispose !== "function") throw new TypeError("the mobile extension mount function must return a disposer or undefined");
228
- activeDispose?.();
229
- activeRoot?.remove();
230
- activeRoot = nextRoot;
231
- activeDispose = typeof nextDispose === "function" ? nextDispose : void 0;
232
- } catch (error) {
233
- nextRoot.remove();
234
- console.error("DSH Mobile custom extension failed to mount", error);
235
- }
236
- } catch {} finally {
237
- loading = false;
238
- }
653
+ const nextDispose = mount({
654
+ document,
655
+ request: mobileRequest,
656
+ root: nextRoot,
657
+ window
658
+ });
659
+ disposeActive?.();
660
+ activeRoot?.remove();
661
+ activeRoot = nextRoot;
662
+ disposeActive = typeof nextDispose === "function" ? nextDispose : void 0;
663
+ } catch {}
239
664
  };
240
- refresh();
241
- const timer = setInterval(() => {
242
- refresh();
665
+ refreshCss();
666
+ refreshJs();
667
+ const timer = window.setInterval(() => {
668
+ refreshCss();
669
+ refreshJs();
243
670
  }, 1e3);
244
671
  return () => {
245
672
  clearInterval(timer);
246
- activeDispose?.();
673
+ disposeActive?.();
247
674
  activeRoot?.remove();
248
- if (previousRegistry === void 0) delete window.dshMobile;
249
- else window.dshMobile = previousRegistry;
675
+ style.remove();
676
+ if (previous === void 0) delete window.dshMobile;
677
+ else window.dshMobile = previous;
250
678
  };
251
679
  }
252
- /** Install the responsive mobile layer and the loopback-only control card. */
680
+ const CONTROL_STYLES = `
681
+ .dsh-mobile-control{position:fixed;z-index:1000;left:16px;bottom:64px;font:14px/1.45 system-ui;color:var(--dsw-alias-label-primary,#16181d)}
682
+ .dsh-mobile-control__panel{box-sizing:border-box;width:min(328px,calc(100vw - 32px));padding:16px;border:1px solid var(--dsw-alias-border-subtle,#e1e5eb);border-radius:18px;background:var(--dsw-alias-bg-layer-2,#fff);box-shadow:0 18px 50px rgb(15 23 42 / 18%)}
683
+ .dsh-mobile-control__header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.dsh-mobile-control__panel h2{margin:0;font-size:17px;line-height:24px}.dsh-mobile-control__close{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:0;border-radius:10px;background:transparent;color:inherit;font-size:24px;line-height:1;cursor:pointer}.dsh-mobile-control__close:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}
684
+ .dsh-mobile-control__access{display:flex;align-items:baseline;gap:6px;min-width:0;margin:0 0 12px}.dsh-mobile-control__access[hidden]{display:none}.dsh-mobile-control__access-label{flex:none;color:var(--dsw-alias-label-secondary,#606873);white-space:nowrap}.dsh-mobile-control__access-label::after{content:":"}.dsh-mobile-control__access-link{min-width:0;overflow:hidden;color:#2563eb;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.dsh-mobile-control__access-link:hover{text-decoration:underline}
685
+ .dsh-mobile-control__status{margin:0 0 14px;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#606873)}.dsh-mobile-control__status::before{display:inline-block;width:8px;height:8px;margin-right:7px;border-radius:50%;background:#98a1ad;content:""}.dsh-mobile-control__status.is-running::before{background:#16a36a}.dsh-mobile-control__status.is-key{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;word-break:break-all}
686
+ .dsh-mobile-control__actions{display:flex;flex-wrap:wrap;gap:8px}.dsh-mobile-control__actions button{min-height:40px;padding:8px 12px;border-radius:11px;font:inherit;cursor:pointer}.dsh-mobile-control__secondary{border:1px solid var(--dsw-alias-border-normal,#cfd5dd);background:transparent;color:inherit}.dsh-mobile-control__primary{border:1px solid #2563eb;background:#2563eb;color:#fff}.dsh-mobile-control__actions button:disabled{cursor:not-allowed;opacity:.45}
687
+ .dsh-mobile-control__trigger{box-sizing:border-box;display:flex;align-items:center;gap:8px;width:calc(100% + 8px);height:34px;margin:4px -4px;padding:6px 2px 6px 10px;border:0;border-radius:12px;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font:14px/22px system-ui;cursor:pointer}.dsh-mobile-control__trigger:hover{background:var(--dsw-alias-interactive-bg-hover,#f1f3f6)}.dsh-mobile-control__trigger.is-rail{width:36px;height:36px;margin:8px 0 10px;padding:0;justify-content:center;border-radius:50%}.dsh-mobile-control__trigger-icon{position:relative;box-sizing:border-box;flex:none;width:14px;height:19px;border:1.7px solid currentColor;border-radius:3px}.dsh-mobile-control__trigger-icon::after{position:absolute;right:4px;bottom:2px;width:4px;height:1.5px;border-radius:2px;background:currentColor;content:""}.dsh-mobile-control__trigger-label{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
688
+ `;
689
+ /** Mount the desktop control or mobile feature enhancements. */
253
690
  function apply(ctx) {
254
691
  ctx.effect(() => {
255
- const style = document.createElement("style");
692
+ const loopback = isLoopbackHost(location.hostname) && !new URLSearchParams(location.search).has("dsh-mobile-preview");
693
+ const style = element("style");
256
694
  style.dataset.plugin = "dsh-mobile";
257
- style.textContent = BASE_STYLES;
695
+ style.textContent = loopback ? CONTROL_STYLES : NATIVE_MOBILE_STYLES;
258
696
  document.head.append(style);
259
- const loopback = isLoopbackHost(location.hostname);
260
- const custom = loopback ? void 0 : document.createElement("style");
261
- let customRefresh;
262
- let customLoading = false;
263
- if (custom !== void 0) {
264
- custom.dataset.plugin = "dsh-mobile";
265
- document.head.append(custom);
266
- const refreshCustomCss = async () => {
267
- if (customLoading || document.visibilityState === "hidden") return;
268
- customLoading = true;
269
- try {
270
- const response = await fetch("/mobile-access/custom.css", {
271
- cache: "no-store",
272
- credentials: "same-origin",
273
- redirect: "error"
274
- });
275
- if (!response.ok || !response.headers.get("content-type")?.startsWith("text/css")) return;
276
- const css = await response.text();
277
- if (custom.textContent !== css) custom.textContent = css;
278
- } catch {} finally {
279
- customLoading = false;
280
- }
697
+ if (!loopback) {
698
+ const removeCustom = installCustomAssets();
699
+ const removeSurface = installNativeMobileSurface();
700
+ return () => {
701
+ removeCustom();
702
+ removeSurface();
703
+ style.remove();
281
704
  };
282
- refreshCustomCss();
283
- customRefresh = setInterval(() => {
284
- refreshCustomCss();
285
- }, 1e3);
286
705
  }
287
- const removeCustomExtension = loopback ? void 0 : installCustomExtension();
288
- const control = loopback ? installControl() : void 0;
289
- const disposeSlot = control === void 0 ? void 0 : ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
706
+ const control = installControl();
707
+ const disposeSlot = ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
290
708
  name: "sidebar.footer.action",
291
709
  id: "dsh-mobile"
292
710
  }, ({ wide }) => (0, react.createElement)("button", {
293
- className: "dsh-mobile-control__trigger",
711
+ "aria-expanded": false,
712
+ "aria-label": "移动访问",
713
+ className: `dsh-mobile-control__trigger${wide ? "" : " is-rail"}`,
294
714
  type: "button",
295
- title: controlLabel(),
715
+ title: "移动访问",
296
716
  onClick: control.toggle
297
- }, wide ? controlLabel() : "M")));
717
+ }, (0, react.createElement)("span", {
718
+ "aria-hidden": true,
719
+ className: "dsh-mobile-control__trigger-icon"
720
+ }), wide ? (0, react.createElement)("span", { className: "dsh-mobile-control__trigger-label" }, "移动访问") : void 0)));
298
721
  return () => {
299
- if (customRefresh !== void 0) clearInterval(customRefresh);
300
- disposeSlot?.();
301
- control?.remove();
302
- removeCustomExtension?.();
303
- custom?.remove();
722
+ disposeSlot();
723
+ control.remove();
304
724
  style.remove();
305
725
  };
306
- }, "dsh-mobile: responsive UI and local control");
726
+ }, "dsh-mobile: stock mobile adaptation and local control");
307
727
  }
308
- /** Client face has no service prerequisites. */
728
+ /** Client services required by the mobile adaptation. */
309
729
  const inject = ["slots"];
310
- function controlLabel() {
311
- return navigator.language.toLowerCase().startsWith("zh") ? "移动端" : "Mobile access";
312
- }
313
730
  //#endregion
314
731
  exports.apply = apply;
315
732
  exports.inject = inject;