@1outres/dsh-plugin-mobile 0.6.0 → 0.7.0
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/README.md +54 -61
- package/lib/client.js +183 -493
- package/lib/index.js +4 -6
- package/locale/en.json +1 -1
- package/locale/ja.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# @1outres/dsh-plugin-mobile
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Adds phone refinements to the DeepSeek Harness Web UI. Harness `0.2.2` already
|
|
4
|
+
turns the sidebar into a modal navigation drawer below 768px, with its own
|
|
5
|
+
opener, mask, focus trap and close-on-navigation. This plugin supplies what is
|
|
6
|
+
still missing on a phone: the page follows the on-screen keyboard, Enter adds a
|
|
7
|
+
line, the keyboard stays closed until you tap the composer, edge swipes open
|
|
8
|
+
and close the shipped drawer, and the layout respects notches and touch.
|
|
8
9
|
|
|
9
10
|
Install just this plugin:
|
|
10
11
|
|
|
@@ -18,16 +19,9 @@ insert the same `mobile` row.
|
|
|
18
19
|
|
|
19
20
|
## On a phone
|
|
20
21
|
|
|
21
|
-
The phone
|
|
22
|
-
|
|
23
|
-
fullscreen).
|
|
22
|
+
The phone refinements apply while the Harness uses its phone layout: below
|
|
23
|
+
768px and without a Windows caption bar.
|
|
24
24
|
|
|
25
|
-
- **Sidebar drawer.** The rail disappears and the conversation takes the full
|
|
26
|
-
width. A menu button before the session title, or a swipe right from the
|
|
27
|
-
left edge, slides the sidebar in over the conversation with a dimmed scrim.
|
|
28
|
-
Tap the scrim, swipe left, or pick a session, a panel or New Session to close
|
|
29
|
-
it. Global panels such as Plugins have no conversation header, so they get a
|
|
30
|
-
floating menu button in the top-left corner instead.
|
|
31
25
|
- **On-screen keyboard.** The page shrinks to the area the keyboard leaves
|
|
32
26
|
visible, so the header stays at the top and the composer sits right above
|
|
33
27
|
the keyboard. The draft box grows to at most 30% of that area.
|
|
@@ -35,21 +29,25 @@ fullscreen).
|
|
|
35
29
|
break and the send button sends. Enter that confirms an IME conversion, and
|
|
36
30
|
Enter in the slash-command or reference menu, keep working as before.
|
|
37
31
|
- **The keyboard opens only when you tap the input.** The Harness focuses the
|
|
38
|
-
composer
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
composer when a composer button such as the model picker or Send is pressed,
|
|
33
|
+
which pops the keyboard over the conversation. On a touch screen the composer
|
|
34
|
+
takes focus only right after a tap on the input (or on the empty part of the
|
|
35
|
+
composer card, or the `+` command button) or a hardware key press; other
|
|
36
|
+
focuses are undone at once.
|
|
37
|
+
- **Swipe the drawer.** Swipe right from the left edge to open the Harness
|
|
38
|
+
navigation drawer, swipe left to close it. The Harness owns the drawer, its
|
|
39
|
+
menu button, its scrim and its close-on-navigation behavior.
|
|
43
40
|
- **Notches and the home indicator.** The page asks for the full screen
|
|
44
|
-
(`viewport-fit=cover`) and pads by the safe-area insets; the
|
|
45
|
-
the
|
|
46
|
-
- **Compact
|
|
47
|
-
|
|
48
|
-
list scrolling sideways above the page.
|
|
41
|
+
(`viewport-fit=cover`) and pads by the safe-area insets; the navigation
|
|
42
|
+
drawer and the composer clear the home indicator.
|
|
43
|
+
- **Compact composer.** The conversation keeps 8px side clearance and the
|
|
44
|
+
draft box caps its height against the visible area.
|
|
49
45
|
- **No accidental reloads.** Pull-to-refresh and overscroll bounce are off, so
|
|
50
46
|
scrolling up through a long conversation does not reload the page.
|
|
47
|
+
- **A full-screen Settings sheet.** The Harness Settings dialog becomes a
|
|
48
|
+
full-screen sheet with its section list scrolling sideways above the page.
|
|
51
49
|
|
|
52
|
-
On any touch screen (also a tablet wider than
|
|
50
|
+
On any touch screen (also a tablet wider than 768px):
|
|
53
51
|
|
|
54
52
|
- Text fields use at least 16px type, so iOS does not zoom in when one gets
|
|
55
53
|
focus.
|
|
@@ -61,15 +59,17 @@ On any touch screen (also a tablet wider than the breakpoint):
|
|
|
61
59
|
28px tap targets, and other sessions keep their time.
|
|
62
60
|
- No tap highlight flash and no double-tap zoom delay on buttons.
|
|
63
61
|
|
|
64
|
-
The plugin also adds the home-screen tags
|
|
65
|
-
`mobile-web-app-capable`, `
|
|
66
|
-
Screen** opens the Harness
|
|
62
|
+
The plugin also patches the viewport metadata and adds the home-screen tags
|
|
63
|
+
(`apple-mobile-web-app-capable`, `mobile-web-app-capable`,
|
|
64
|
+
`apple-mobile-web-app-title`), so **Add to Home Screen** opens the Harness
|
|
65
|
+
without the browser bar. The Harness already ships the manifest and the
|
|
66
|
+
home-screen icon.
|
|
67
67
|
|
|
68
68
|
### With the other plugins in this repository
|
|
69
69
|
|
|
70
70
|
- [`@1outres/dsh-plugin-background`](../dsh-plugin-background) keeps the
|
|
71
|
-
drawer and the fullscreen right panel opaque, so
|
|
72
|
-
show through them.
|
|
71
|
+
Harness drawer, the Settings sheet and the fullscreen right panel opaque, so
|
|
72
|
+
the conversation does not show through them.
|
|
73
73
|
- [`@1outres/dsh-plugin-model-picker`](../dsh-plugin-model-picker) does not
|
|
74
74
|
focus its search field on a touch screen, fits above the keyboard, and stays
|
|
75
75
|
open when the keyboard is dismissed, so the Effort row stays reachable.
|
|
@@ -78,32 +78,30 @@ Screen** opens the Harness without the browser bar.
|
|
|
78
78
|
|
|
79
79
|
Edit them in **Settings → Mobile**, or in the profile patch under the
|
|
80
80
|
`mobile` row. Every change applies immediately in every open page. The page
|
|
81
|
-
also says whether the
|
|
82
|
-
|
|
81
|
+
also says whether the Harness currently uses its phone layout, and at which
|
|
82
|
+
width.
|
|
83
83
|
|
|
84
|
-
| Field | Default | Meaning
|
|
85
|
-
| -------------------- | --------- |
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `blockPullToRefresh` | `true` | Stop pull-to-refresh and overscroll bounce. |
|
|
92
|
-
| `autoFocusComposer` | `false` | Let the composer focus itself (and open the keyboard) on a touch screen, as on desktop. |
|
|
84
|
+
| Field | Default | Meaning |
|
|
85
|
+
| -------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `enterKey` | `newline` | Enter in the composer on a touch screen: `newline` adds a line, `send` keeps the Harness default. |
|
|
87
|
+
| `swipe` | `true` | Edge swipe opens the Harness navigation drawer, swipe left closes it. |
|
|
88
|
+
| `fitKeyboard` | `true` | Fit the page to the area the on-screen keyboard leaves visible. |
|
|
89
|
+
| `blockPullToRefresh` | `true` | Stop pull-to-refresh and overscroll bounce. |
|
|
90
|
+
| `autoFocusComposer` | `false` | Let a composer button or another Harness action focus the input (and open the keyboard). Off keeps the keyboard closed until you tap the input. |
|
|
93
91
|
|
|
94
|
-
|
|
92
|
+
The Harness fixes the drawer width at 768px; this plugin has no separate
|
|
93
|
+
breakpoint. The earlier `mode` and `breakpoint` fields are gone; an old profile
|
|
94
|
+
entry that still sets them is ignored.
|
|
95
95
|
|
|
96
96
|
## How it works
|
|
97
97
|
|
|
98
|
-
1. The browser half watches
|
|
99
|
-
`data-dsh-
|
|
100
|
-
attributes, does the layout
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`conversation.header.leading` slot; the scrim and the floating button live
|
|
106
|
-
in `shell.overlay`.
|
|
98
|
+
1. The browser half watches the frame's `data-sidebar-drawer` attribute and a
|
|
99
|
+
coarse-pointer media query, and sets `data-dsh-mobile` and `data-dsh-touch`
|
|
100
|
+
on `<html>`. One stylesheet, scoped under those attributes, does the layout
|
|
101
|
+
work.
|
|
102
|
+
2. The drawer belongs to the Harness. The plugin only calls
|
|
103
|
+
`ctx.layout.toggleSidebar()` for an edge swipe and reads the frame's
|
|
104
|
+
`data-sidebar-collapsed` attribute to know whether it is open.
|
|
107
105
|
3. The keyboard fit reads `window.visualViewport` and publishes its height as
|
|
108
106
|
`--dsh-mobile-vvh`; the page height follows it. iOS scrolls the whole
|
|
109
107
|
document to reveal a focused field, so that scroll is undone while the
|
|
@@ -120,21 +118,16 @@ which width, to help pick the breakpoint.
|
|
|
120
118
|
## Limits
|
|
121
119
|
|
|
122
120
|
- **It restyles shipped UI from the outside.** Host elements are found through
|
|
123
|
-
|
|
124
|
-
`data-
|
|
121
|
+
data attributes (`data-sidebar-drawer`, `data-sidebar-collapsed`,
|
|
122
|
+
`data-rightbar-col`, `data-composer-*`, `data-conversation-*`,
|
|
125
123
|
`data-shortcut-modal`) and ARIA roles. Two touch rules match Session row
|
|
126
124
|
parts by CSS-module class name. A Harness update within
|
|
127
|
-
`>=0.2.
|
|
125
|
+
`>=0.2.2-rc.1 <0.3.0` can rename any of them; the matching rule then stops
|
|
128
126
|
applying, and the page falls back to the shipped layout for that part.
|
|
129
127
|
- Enter relies on the composer being a plain-text editor that turns the
|
|
130
128
|
browser's paragraph insertion into a line break.
|
|
131
|
-
- The
|
|
132
|
-
|
|
133
|
-
- Between the breakpoint and about 756px a normal right panel overlaps the
|
|
134
|
-
conversation instead of squeezing it; below 768px the Harness already opens
|
|
135
|
-
it fullscreen.
|
|
136
|
-
- iOS shows a screenshot instead of an icon on the home screen: it does not
|
|
137
|
-
use SVG touch icons, and the Harness ships no PNG.
|
|
129
|
+
- The Harness owns the drawer breakpoint; the plugin follows the frame's
|
|
130
|
+
`data-sidebar-drawer` attribute rather than repeating the width.
|
|
138
131
|
- An edge swipe in Safari can also trigger the browser's own back gesture; the
|
|
139
132
|
home-screen app has none.
|
|
140
133
|
- Buttons and the Settings page are in English, Chinese and Japanese (the
|
package/lib/client.js
CHANGED
|
@@ -26,32 +26,57 @@ window.__ModuleLoader__.load({
|
|
|
26
26
|
// src/client/index.tsx
|
|
27
27
|
var index_exports = {};
|
|
28
28
|
__export(index_exports, {
|
|
29
|
-
HEADER_SLOT: () => HEADER_SLOT,
|
|
30
|
-
OVERLAY_SLOT: () => OVERLAY_SLOT,
|
|
31
29
|
SETTINGS_SLOT: () => SETTINGS_SLOT,
|
|
32
30
|
apply: () => apply,
|
|
33
31
|
inject: () => inject
|
|
34
32
|
});
|
|
35
33
|
module.exports = __toCommonJS(index_exports);
|
|
36
34
|
|
|
37
|
-
// src/client/
|
|
38
|
-
var
|
|
35
|
+
// src/client/config.ts
|
|
36
|
+
var NAMESPACE = "mobile";
|
|
37
|
+
var DEFAULT_CONFIG = {
|
|
38
|
+
enterKey: "newline",
|
|
39
|
+
swipe: true,
|
|
40
|
+
fitKeyboard: true,
|
|
41
|
+
blockPullToRefresh: true,
|
|
42
|
+
autoFocusComposer: false
|
|
43
|
+
};
|
|
44
|
+
var ENTER_KEYS = ["newline", "send"];
|
|
45
|
+
function oneOf(value, allowed, fallback) {
|
|
46
|
+
return typeof value === "string" && allowed.includes(value) ? value : fallback;
|
|
47
|
+
}
|
|
48
|
+
function flag(value, fallback) {
|
|
49
|
+
return typeof value === "boolean" ? value : fallback;
|
|
50
|
+
}
|
|
51
|
+
function normalizeConfig(value) {
|
|
52
|
+
return {
|
|
53
|
+
enterKey: oneOf(value?.enterKey, ENTER_KEYS, DEFAULT_CONFIG.enterKey),
|
|
54
|
+
swipe: flag(value?.swipe, DEFAULT_CONFIG.swipe),
|
|
55
|
+
fitKeyboard: flag(value?.fitKeyboard, DEFAULT_CONFIG.fitKeyboard),
|
|
56
|
+
blockPullToRefresh: flag(value?.blockPullToRefresh, DEFAULT_CONFIG.blockPullToRefresh),
|
|
57
|
+
autoFocusComposer: flag(value?.autoFocusComposer, DEFAULT_CONFIG.autoFocusComposer)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
39
60
|
|
|
40
61
|
// src/client/frame.ts
|
|
41
|
-
var
|
|
42
|
-
var FRAME = "div:has(> [data-rightbar-col])";
|
|
62
|
+
var FRAME_ANCHOR = "[data-rightbar-col]";
|
|
43
63
|
var COLLAPSED_ATTRIBUTE = "data-sidebar-collapsed";
|
|
44
64
|
var FULLSCREEN_ATTRIBUTE = "data-rightbar-fullscreen";
|
|
65
|
+
var DRAWER_MODAL = "sidebar";
|
|
45
66
|
function findFrame(root) {
|
|
46
|
-
return root.querySelector(
|
|
67
|
+
return root.querySelector(FRAME_ANCHOR)?.parentElement ?? null;
|
|
47
68
|
}
|
|
48
69
|
function isExpanded(frame) {
|
|
49
70
|
return frame !== null && !frame.hasAttribute(COLLAPSED_ATTRIBUTE);
|
|
50
71
|
}
|
|
51
|
-
var NO_SWIPE = "pre, table, input, textarea, select, [contenteditable='true'], [role='slider'], [data-dsh-mobile-no-swipe]";
|
|
72
|
+
var NO_SWIPE = "pre, table, input, textarea, select, [contenteditable='true'], [role='slider'], [data-sidebar-opener], [data-sidebar-toggle], [data-dsh-mobile-no-swipe]";
|
|
73
|
+
var OTHER_MODAL = `[aria-modal='true']:not([data-shortcut-modal='${DRAWER_MODAL}'])`;
|
|
74
|
+
function otherModalOpen(root) {
|
|
75
|
+
return root.querySelector(OTHER_MODAL) !== null;
|
|
76
|
+
}
|
|
52
77
|
function swipeBlocked(target, root, overflowX) {
|
|
53
78
|
if (findFrame(root)?.hasAttribute(FULLSCREEN_ATTRIBUTE) === true) return true;
|
|
54
|
-
if (root
|
|
79
|
+
if (otherModalOpen(root)) return true;
|
|
55
80
|
const element = target;
|
|
56
81
|
if (element === null || typeof element.closest !== "function") return false;
|
|
57
82
|
if (element.closest(NO_SWIPE) !== null) return true;
|
|
@@ -65,146 +90,58 @@ window.__ModuleLoader__.load({
|
|
|
65
90
|
}
|
|
66
91
|
return false;
|
|
67
92
|
}
|
|
68
|
-
function useFrameFlag(anchor, attribute) {
|
|
69
|
-
const subscribe = (0, import_react.useCallback)(
|
|
70
|
-
(listener) => {
|
|
71
|
-
const frame = anchor.current?.closest(FRAME);
|
|
72
|
-
if (frame === void 0 || frame === null) return () => {
|
|
73
|
-
};
|
|
74
|
-
const observer = new MutationObserver(listener);
|
|
75
|
-
observer.observe(frame, { attributes: true, attributeFilter: [attribute] });
|
|
76
|
-
return () => observer.disconnect();
|
|
77
|
-
},
|
|
78
|
-
[anchor, attribute]
|
|
79
|
-
);
|
|
80
|
-
const read = (0, import_react.useCallback)(
|
|
81
|
-
() => anchor.current?.closest(FRAME)?.hasAttribute(attribute),
|
|
82
|
-
[anchor, attribute]
|
|
83
|
-
);
|
|
84
|
-
return (0, import_react.useSyncExternalStore)(subscribe, read, () => void 0);
|
|
85
|
-
}
|
|
86
93
|
|
|
87
|
-
// src/client/
|
|
88
|
-
var
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
// src/client/drawer.ts
|
|
95
|
+
var DRAWER_ATTRIBUTE = "data-sidebar-drawer";
|
|
96
|
+
function observeMutations(target, options, callback) {
|
|
97
|
+
const observer = new MutationObserver(callback);
|
|
98
|
+
observer.observe(target, options);
|
|
99
|
+
return observer;
|
|
100
|
+
}
|
|
101
|
+
function createDrawerSource(document2, observe) {
|
|
102
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
103
|
+
let frame = null;
|
|
104
|
+
let frameObserver;
|
|
105
|
+
let value = false;
|
|
106
|
+
const readFrame = () => document2.querySelector(FRAME_ANCHOR)?.parentElement ?? null;
|
|
107
|
+
const refresh = () => {
|
|
108
|
+
const next = frame?.hasAttribute(DRAWER_ATTRIBUTE) ?? false;
|
|
109
|
+
if (next === value) return;
|
|
110
|
+
value = next;
|
|
111
|
+
for (const listener of [...listeners]) listener();
|
|
112
|
+
};
|
|
113
|
+
const attach = () => {
|
|
114
|
+
const next = readFrame();
|
|
115
|
+
if (next === frame) {
|
|
116
|
+
refresh();
|
|
117
|
+
return;
|
|
100
118
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const getSnapshot = (0, import_react2.useCallback)(() => observable.getSnapshot(), [observable]);
|
|
112
|
-
return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
113
|
-
}
|
|
114
|
-
function mainSessionOf(state) {
|
|
115
|
-
for (const [id, row] of Object.entries(state.byId)) {
|
|
116
|
-
if ((row?.retainedBy?.mainView ?? 0) > 0) return id;
|
|
117
|
-
}
|
|
118
|
-
return void 0;
|
|
119
|
-
}
|
|
120
|
-
function HeaderMenuButton({ mode, toggle, t }) {
|
|
121
|
-
const state = useObservable(mode);
|
|
122
|
-
const ref = (0, import_react2.useRef)(null);
|
|
123
|
-
const collapsed = useFrameFlag(ref, COLLAPSED_ATTRIBUTE);
|
|
124
|
-
const expanded = collapsed === false;
|
|
125
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
126
|
-
"button",
|
|
127
|
-
{
|
|
128
|
-
ref,
|
|
129
|
-
type: "button",
|
|
130
|
-
className: "dsh-mobile-menu",
|
|
131
|
-
hidden: !state.mobile,
|
|
132
|
-
"aria-label": expanded ? t("menu.close") : t("menu.open"),
|
|
133
|
-
"aria-expanded": state.mobile ? expanded : void 0,
|
|
134
|
-
onClick: toggle,
|
|
135
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconMenu, { size: 18 })
|
|
119
|
+
frameObserver?.disconnect();
|
|
120
|
+
frame = next;
|
|
121
|
+
frameObserver = frame === null ? void 0 : observe(frame, { attributes: true, attributeFilter: [DRAWER_ATTRIBUTE] }, refresh);
|
|
122
|
+
refresh();
|
|
123
|
+
};
|
|
124
|
+
const documentObserver = observe(
|
|
125
|
+
document2.documentElement,
|
|
126
|
+
{ childList: true, subtree: true },
|
|
127
|
+
() => {
|
|
128
|
+
if (frame === null || frame.isConnected === false) attach();
|
|
136
129
|
}
|
|
137
130
|
);
|
|
138
|
-
|
|
139
|
-
function DrawerOverlay({ mode, toggle, t, usePanelInfo, useSessions }) {
|
|
140
|
-
const state = useObservable(mode);
|
|
141
|
-
const anchor = (0, import_react2.useRef)(null);
|
|
142
|
-
const collapsed = useFrameFlag(anchor, COLLAPSED_ATTRIBUTE);
|
|
143
|
-
const expanded = state.mobile && collapsed === false;
|
|
144
|
-
const panel = usePanelInfo((info) => info.activePanelId);
|
|
145
|
-
const session = useSessions(mainSessionOf);
|
|
146
|
-
const seen = (0, import_react2.useRef)({ panel, session });
|
|
147
|
-
(0, import_react2.useEffect)(() => {
|
|
148
|
-
const before = seen.current;
|
|
149
|
-
seen.current = { panel, session };
|
|
150
|
-
if (before.panel === panel && before.session === session) return;
|
|
151
|
-
if (expanded) toggle();
|
|
152
|
-
}, [panel, session, expanded, toggle]);
|
|
153
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
154
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { ref: anchor, hidden: true }),
|
|
155
|
-
state.mobile ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
156
|
-
"div",
|
|
157
|
-
{
|
|
158
|
-
className: "dsh-mobile-scrim",
|
|
159
|
-
"data-open": expanded ? "" : void 0,
|
|
160
|
-
"aria-hidden": "true",
|
|
161
|
-
onClick: toggle
|
|
162
|
-
}
|
|
163
|
-
) : null,
|
|
164
|
-
state.mobile && panel !== null && !expanded ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
165
|
-
"button",
|
|
166
|
-
{
|
|
167
|
-
type: "button",
|
|
168
|
-
className: "dsh-mobile-menu dsh-mobile-floating",
|
|
169
|
-
"aria-label": t("menu.open"),
|
|
170
|
-
onClick: toggle,
|
|
171
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IconMenu, { size: 18 })
|
|
172
|
-
}
|
|
173
|
-
) : null
|
|
174
|
-
] });
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// src/client/config.ts
|
|
178
|
-
var NAMESPACE = "mobile";
|
|
179
|
-
var MIN_BREAKPOINT = 360;
|
|
180
|
-
var MAX_BREAKPOINT = 1024;
|
|
181
|
-
var DEFAULT_CONFIG = {
|
|
182
|
-
mode: "auto",
|
|
183
|
-
breakpoint: 768,
|
|
184
|
-
enterKey: "newline",
|
|
185
|
-
swipe: true,
|
|
186
|
-
fitKeyboard: true,
|
|
187
|
-
blockPullToRefresh: true,
|
|
188
|
-
autoFocusComposer: false
|
|
189
|
-
};
|
|
190
|
-
var MODES = ["auto", "always", "off"];
|
|
191
|
-
var ENTER_KEYS = ["newline", "send"];
|
|
192
|
-
function oneOf(value, allowed, fallback) {
|
|
193
|
-
return typeof value === "string" && allowed.includes(value) ? value : fallback;
|
|
194
|
-
}
|
|
195
|
-
function flag(value, fallback) {
|
|
196
|
-
return typeof value === "boolean" ? value : fallback;
|
|
197
|
-
}
|
|
198
|
-
function normalizeConfig(value) {
|
|
199
|
-
const breakpoint = typeof value?.breakpoint === "number" && Number.isFinite(value.breakpoint) ? Math.round(Math.min(MAX_BREAKPOINT, Math.max(MIN_BREAKPOINT, value.breakpoint))) : DEFAULT_CONFIG.breakpoint;
|
|
131
|
+
attach();
|
|
200
132
|
return {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
133
|
+
get: () => value,
|
|
134
|
+
subscribe(listener) {
|
|
135
|
+
listeners.add(listener);
|
|
136
|
+
return () => {
|
|
137
|
+
listeners.delete(listener);
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
dispose() {
|
|
141
|
+
documentObserver.disconnect();
|
|
142
|
+
frameObserver?.disconnect();
|
|
143
|
+
listeners.clear();
|
|
144
|
+
}
|
|
208
145
|
};
|
|
209
146
|
}
|
|
210
147
|
|
|
@@ -291,29 +228,19 @@ window.__ModuleLoader__.load({
|
|
|
291
228
|
|
|
292
229
|
// src/client/i18n.ts
|
|
293
230
|
var en = {
|
|
294
|
-
"menu.open": "Open sidebar",
|
|
295
|
-
"menu.close": "Close sidebar",
|
|
296
231
|
nav: "Mobile",
|
|
297
232
|
"settings.title": "Mobile",
|
|
298
|
-
"settings.description": "
|
|
299
|
-
"settings.statusMobile": "
|
|
300
|
-
"settings.statusDesktop": "
|
|
301
|
-
"settings.mode": "Phone layout",
|
|
302
|
-
"settings.modeHint": "Auto switches below the breakpoint. Off removes every change of this plugin.",
|
|
303
|
-
"settings.mode.auto": "Auto",
|
|
304
|
-
"settings.mode.always": "Always",
|
|
305
|
-
"settings.mode.off": "Off",
|
|
306
|
-
"settings.breakpoint": "Breakpoint",
|
|
307
|
-
"settings.breakpointHint": "Viewport width in CSS pixels, from 360 to 1024. Below it, auto mode uses the phone layout. Most phones are 360 to 430 wide in portrait.",
|
|
308
|
-
"settings.breakpointInvalid": "Enter a whole number from 360 to 1024.",
|
|
233
|
+
"settings.description": "Adds phone refinements on top of the Harness layout: the page fits above the on-screen keyboard, Enter adds a line, the keyboard stays closed until you tap the composer, and an edge swipe opens the navigation drawer that the Harness uses below 768px. Changes apply at once in every open page.",
|
|
234
|
+
"settings.statusMobile": "The Harness is using its phone layout here ({width}px wide).",
|
|
235
|
+
"settings.statusDesktop": "The Harness is using its desktop layout here ({width}px wide); it switches to the phone layout below 768px.",
|
|
309
236
|
"settings.enterKey": "Enter key in the composer",
|
|
310
237
|
"settings.enterKeyHint": "On a touch screen in the phone layout. With New line, send with the send button. IME conversion and command menus keep their Enter.",
|
|
311
238
|
"settings.enterKey.newline": "New line",
|
|
312
239
|
"settings.enterKey.send": "Send",
|
|
313
240
|
"settings.autoFocusComposer": "Focus the composer automatically",
|
|
314
|
-
"settings.autoFocusComposerHint": "
|
|
241
|
+
"settings.autoFocusComposerHint": "Let a composer button or another Harness action focus the input and open the on-screen keyboard. Off keeps the keyboard closed until you tap the input.",
|
|
315
242
|
"settings.swipe": "Swipe to open the sidebar",
|
|
316
|
-
"settings.swipeHint": "Swipe right from the left edge to open, swipe left to close.",
|
|
243
|
+
"settings.swipeHint": "Swipe right from the left edge to open the Harness navigation drawer, swipe left to close it.",
|
|
317
244
|
"settings.fitKeyboard": "Fit the page to the keyboard",
|
|
318
245
|
"settings.fitKeyboardHint": "Keep the header and the composer on screen while the on-screen keyboard is up.",
|
|
319
246
|
"settings.blockPullToRefresh": "Block pull-to-refresh",
|
|
@@ -324,29 +251,19 @@ window.__ModuleLoader__.load({
|
|
|
324
251
|
"settings.writeError": "The Host refused the change."
|
|
325
252
|
};
|
|
326
253
|
var zh = {
|
|
327
|
-
"menu.open": "\u6253\u5F00\u4FA7\u8FB9\u680F",
|
|
328
|
-
"menu.close": "\u5173\u95ED\u4FA7\u8FB9\u680F",
|
|
329
254
|
nav: "\u79FB\u52A8\u7AEF",
|
|
330
255
|
"settings.title": "\u79FB\u52A8\u7AEF",
|
|
331
|
-
"settings.description": "\
|
|
332
|
-
"settings.statusMobile": "\u5F53\u524D\
|
|
333
|
-
"settings.statusDesktop": "\u5F53\u524D\
|
|
334
|
-
"settings.mode": "\u624B\u673A\u5E03\u5C40",
|
|
335
|
-
"settings.modeHint": "\u81EA\u52A8\uFF1A\u7A84\u4E8E\u65AD\u70B9\u65F6\u5207\u6362\u3002\u5173\u95ED\uFF1A\u64A4\u9500\u672C\u63D2\u4EF6\u7684\u6240\u6709\u4FEE\u6539\u3002",
|
|
336
|
-
"settings.mode.auto": "\u81EA\u52A8",
|
|
337
|
-
"settings.mode.always": "\u59CB\u7EC8",
|
|
338
|
-
"settings.mode.off": "\u5173\u95ED",
|
|
339
|
-
"settings.breakpoint": "\u65AD\u70B9",
|
|
340
|
-
"settings.breakpointHint": "\u89C6\u53E3\u5BBD\u5EA6\uFF08CSS \u50CF\u7D20\uFF09\uFF0C360 \u5230 1024\u3002\u7A84\u4E8E\u8BE5\u503C\u65F6\u81EA\u52A8\u6A21\u5F0F\u4F7F\u7528\u624B\u673A\u5E03\u5C40\u3002\u591A\u6570\u624B\u673A\u7AD6\u5C4F\u5BBD 360 \u5230 430\u3002",
|
|
341
|
-
"settings.breakpointInvalid": "\u8BF7\u8F93\u5165 360 \u5230 1024 \u4E4B\u95F4\u7684\u6574\u6570\u3002",
|
|
256
|
+
"settings.description": "\u5728 Harness \u5E03\u5C40\u4E4B\u4E0A\u8865\u5145\u624B\u673A\u4F53\u9A8C\uFF1A\u9875\u9762\u9002\u914D\u5C4F\u5E55\u952E\u76D8\u4E0A\u65B9\u7684\u53EF\u89C1\u533A\u57DF\uFF0CEnter \u6362\u884C\uFF0C\u70B9\u6309\u8F93\u5165\u6846\u4E4B\u524D\u4E0D\u5F39\u51FA\u952E\u76D8\uFF0C\u5C4F\u5E55\u7A84\u4E8E 768px \u65F6\u4ECE\u8FB9\u7F18\u6ED1\u52A8\u53EF\u6253\u5F00 Harness \u7684\u5BFC\u822A\u62BD\u5C49\u3002\u4FEE\u6539\u4F1A\u7ACB\u5373\u5E94\u7528\u5230\u6240\u6709\u6253\u5F00\u7684\u9875\u9762\u3002",
|
|
257
|
+
"settings.statusMobile": "Harness \u5F53\u524D\u4F7F\u7528\u624B\u673A\u5E03\u5C40\uFF08\u5BBD {width}px\uFF09\u3002",
|
|
258
|
+
"settings.statusDesktop": "Harness \u5F53\u524D\u4F7F\u7528\u684C\u9762\u5E03\u5C40\uFF08\u5BBD {width}px\uFF09\uFF1B\u7A84\u4E8E 768px \u65F6\u5207\u6362\u4E3A\u624B\u673A\u5E03\u5C40\u3002",
|
|
342
259
|
"settings.enterKey": "\u8F93\u5165\u6846\u4E2D\u7684 Enter \u952E",
|
|
343
260
|
"settings.enterKeyHint": "\u4EC5\u5728\u89E6\u5C4F\u8BBE\u5907\u7684\u624B\u673A\u5E03\u5C40\u4E2D\u751F\u6548\u3002\u9009\u62E9\u6362\u884C\u65F6\u7528\u53D1\u9001\u6309\u94AE\u53D1\u9001\u3002\u8F93\u5165\u6CD5\u786E\u8BA4\u548C\u547D\u4EE4\u83DC\u5355\u4ECD\u4F7F\u7528 Enter\u3002",
|
|
344
261
|
"settings.enterKey.newline": "\u6362\u884C",
|
|
345
262
|
"settings.enterKey.send": "\u53D1\u9001",
|
|
346
263
|
"settings.autoFocusComposer": "\u81EA\u52A8\u805A\u7126\u8F93\u5165\u6846",
|
|
347
|
-
"settings.autoFocusComposerHint": "\
|
|
264
|
+
"settings.autoFocusComposerHint": "\u6309\u4E0B\u8F93\u5165\u6846\u4E2D\u7684\u6309\u94AE\u6216\u7531 Harness \u7684\u5176\u4ED6\u64CD\u4F5C\u628A\u7126\u70B9\u79FB\u5230\u8F93\u5165\u6846\u65F6\uFF0C\u5141\u8BB8\u805A\u7126\u5E76\u5F39\u51FA\u5C4F\u5E55\u952E\u76D8\u3002\u5173\u95ED\u65F6\uFF0C\u70B9\u6309\u8F93\u5165\u6846\u4E4B\u524D\u952E\u76D8\u4E0D\u4F1A\u5F39\u51FA\u3002",
|
|
348
265
|
"settings.swipe": "\u6ED1\u52A8\u6253\u5F00\u4FA7\u8FB9\u680F",
|
|
349
|
-
"settings.swipeHint": "\u4ECE\u5DE6\u8FB9\u7F18\u5411\u53F3\u6ED1\u52A8\u6253\u5F00\uFF0C\u5411\u5DE6\u6ED1\u52A8\u5173\u95ED\u3002",
|
|
266
|
+
"settings.swipeHint": "\u4ECE\u5DE6\u8FB9\u7F18\u5411\u53F3\u6ED1\u52A8\u6253\u5F00 Harness \u5BFC\u822A\u62BD\u5C49\uFF0C\u5411\u5DE6\u6ED1\u52A8\u5173\u95ED\u3002",
|
|
350
267
|
"settings.fitKeyboard": "\u9875\u9762\u9002\u914D\u952E\u76D8",
|
|
351
268
|
"settings.fitKeyboardHint": "\u5C4F\u5E55\u952E\u76D8\u5F39\u51FA\u65F6\uFF0C\u6807\u9898\u680F\u548C\u8F93\u5165\u6846\u4FDD\u6301\u5728\u5C4F\u5E55\u5185\u3002",
|
|
352
269
|
"settings.blockPullToRefresh": "\u963B\u6B62\u4E0B\u62C9\u5237\u65B0",
|
|
@@ -357,29 +274,19 @@ window.__ModuleLoader__.load({
|
|
|
357
274
|
"settings.writeError": "Host \u62D2\u7EDD\u4E86\u6B64\u4FEE\u6539\u3002"
|
|
358
275
|
};
|
|
359
276
|
var ja = {
|
|
360
|
-
"menu.open": "\u30B5\u30A4\u30C9\u30D0\u30FC\u3092\u958B\u304F",
|
|
361
|
-
"menu.close": "\u30B5\u30A4\u30C9\u30D0\u30FC\u3092\u9589\u3058\u308B",
|
|
362
277
|
nav: "\u30E2\u30D0\u30A4\u30EB",
|
|
363
278
|
"settings.title": "\u30E2\u30D0\u30A4\u30EB",
|
|
364
|
-
"settings.description": "
|
|
365
|
-
"settings.statusMobile": "\u3053\u306E\u30DA\u30FC\u30B8\
|
|
366
|
-
"settings.statusDesktop": "\u3053\u306E\u30DA\u30FC\u30B8\
|
|
367
|
-
"settings.mode": "\u30B9\u30DE\u30DB\u8868\u793A",
|
|
368
|
-
"settings.modeHint": "\u81EA\u52D5\u306F\u30D6\u30EC\u30FC\u30AF\u30DD\u30A4\u30F3\u30C8\u3088\u308A\u72ED\u3044\u3068\u304D\u306B\u5207\u308A\u66FF\u3048\u307E\u3059\u3002\u30AA\u30D5\u306F\u3053\u306E\u30D7\u30E9\u30B0\u30A4\u30F3\u306E\u5909\u66F4\u3092\u3059\u3079\u3066\u5916\u3057\u307E\u3059\u3002",
|
|
369
|
-
"settings.mode.auto": "\u81EA\u52D5",
|
|
370
|
-
"settings.mode.always": "\u5E38\u306B",
|
|
371
|
-
"settings.mode.off": "\u30AA\u30D5",
|
|
372
|
-
"settings.breakpoint": "\u30D6\u30EC\u30FC\u30AF\u30DD\u30A4\u30F3\u30C8",
|
|
373
|
-
"settings.breakpointHint": "\u30D3\u30E5\u30FC\u30DD\u30FC\u30C8\u5E45\uFF08CSS \u30D4\u30AF\u30BB\u30EB\uFF09\u3002360\u301C1024 \u306E\u7BC4\u56F2\u3067\u3059\u3002\u3053\u308C\u3088\u308A\u72ED\u3044\u3068\u81EA\u52D5\u30E2\u30FC\u30C9\u3067\u30B9\u30DE\u30DB\u8868\u793A\u306B\u306A\u308A\u307E\u3059\u3002\u7E26\u5411\u304D\u306E\u30B9\u30DE\u30DB\u306F\u304A\u304A\u3080\u306D 360\u301C430 \u3067\u3059\u3002",
|
|
374
|
-
"settings.breakpointInvalid": "360\u301C1024 \u306E\u6574\u6570\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
279
|
+
"settings.description": "Harness \u306E\u30EC\u30A4\u30A2\u30A6\u30C8\u306B\u30B9\u30DE\u30FC\u30C8\u30D5\u30A9\u30F3\u5411\u3051\u306E\u8ABF\u6574\u3092\u8DB3\u3057\u307E\u3059\u3002\u30AA\u30F3\u30B9\u30AF\u30EA\u30FC\u30F3\u30AD\u30FC\u30DC\u30FC\u30C9\u306E\u4E0A\u306B\u53CE\u307E\u308B\u3088\u3046\u30DA\u30FC\u30B8\u3092\u7E2E\u3081\u3001Enter \u3067\u6539\u884C\u3057\u3001\u5165\u529B\u6B04\u3092\u30BF\u30C3\u30D7\u3059\u308B\u307E\u3067\u30AD\u30FC\u30DC\u30FC\u30C9\u3092\u958B\u304B\u305A\u3001768px \u672A\u6E80\u3067\u4F7F\u308F\u308C\u308B Harness \u306E\u30CA\u30D3\u30B2\u30FC\u30B7\u30E7\u30F3\u30C9\u30ED\u30EF\u30FC\u3092\u753B\u9762\u7AEF\u306E\u30B9\u30EF\u30A4\u30D7\u3067\u958B\u9589\u3067\u304D\u308B\u3088\u3046\u306B\u3057\u307E\u3059\u3002\u5909\u66F4\u306F\u958B\u3044\u3066\u3044\u308B\u3059\u3079\u3066\u306E\u30DA\u30FC\u30B8\u306B\u3059\u3050\u53CD\u6620\u3055\u308C\u307E\u3059\u3002",
|
|
280
|
+
"settings.statusMobile": "Harness \u306F\u3053\u306E\u30DA\u30FC\u30B8\u3067\u30B9\u30DE\u30DB\u8868\u793A\u3092\u4F7F\u3063\u3066\u3044\u307E\u3059\uFF08\u5E45 {width}px\uFF09\u3002",
|
|
281
|
+
"settings.statusDesktop": "Harness \u306F\u3053\u306E\u30DA\u30FC\u30B8\u3067\u30C7\u30B9\u30AF\u30C8\u30C3\u30D7\u8868\u793A\u3092\u4F7F\u3063\u3066\u3044\u307E\u3059\uFF08\u5E45 {width}px\uFF09\u3002768px \u672A\u6E80\u3067\u30B9\u30DE\u30DB\u8868\u793A\u306B\u5207\u308A\u66FF\u308F\u308A\u307E\u3059\u3002",
|
|
375
282
|
"settings.enterKey": "\u5165\u529B\u6B04\u306E Enter \u30AD\u30FC",
|
|
376
283
|
"settings.enterKeyHint": "\u30BF\u30C3\u30C1\u7AEF\u672B\u306E\u30B9\u30DE\u30DB\u8868\u793A\u3067\u3060\u3051\u52B9\u304D\u307E\u3059\u3002\u300C\u6539\u884C\u300D\u306E\u3068\u304D\u306F\u9001\u4FE1\u30DC\u30BF\u30F3\u3067\u9001\u308A\u307E\u3059\u3002\u5909\u63DB\u306E\u78BA\u5B9A\u3068\u30B3\u30DE\u30F3\u30C9\u30E1\u30CB\u30E5\u30FC\u3067\u306F\u5F93\u6765\u3069\u304A\u308A Enter \u304C\u52B9\u304D\u307E\u3059\u3002",
|
|
377
284
|
"settings.enterKey.newline": "\u6539\u884C",
|
|
378
285
|
"settings.enterKey.send": "\u9001\u4FE1",
|
|
379
286
|
"settings.autoFocusComposer": "\u5165\u529B\u6B04\u306B\u81EA\u52D5\u3067\u30D5\u30A9\u30FC\u30AB\u30B9\u3059\u308B",
|
|
380
|
-
"settings.autoFocusComposerHint": "\
|
|
287
|
+
"settings.autoFocusComposerHint": "\u5165\u529B\u6B04\u306E\u30DC\u30BF\u30F3\u3084 Harness \u306E\u307B\u304B\u306E\u64CD\u4F5C\u3067\u5165\u529B\u6B04\u306B\u30D5\u30A9\u30FC\u30AB\u30B9\u304C\u79FB\u3063\u305F\u3068\u304D\u3001\u305D\u306E\u307E\u307E\u30D5\u30A9\u30FC\u30AB\u30B9\u3057\u3066\u30AD\u30FC\u30DC\u30FC\u30C9\u3092\u958B\u304D\u307E\u3059\u3002\u30AA\u30D5\u306B\u3059\u308B\u3068\u3001\u5165\u529B\u6B04\u3092\u30BF\u30C3\u30D7\u3059\u308B\u307E\u3067\u30AD\u30FC\u30DC\u30FC\u30C9\u306F\u958B\u304D\u307E\u305B\u3093\u3002",
|
|
381
288
|
"settings.swipe": "\u30B9\u30EF\u30A4\u30D7\u3067\u30B5\u30A4\u30C9\u30D0\u30FC\u3092\u958B\u304F",
|
|
382
|
-
"settings.swipeHint": "\u5DE6\u7AEF\u304B\u3089\u53F3\u3078\u30B9\u30EF\u30A4\u30D7\u3067\u958B\u304D\u3001\u5DE6\u3078\u30B9\u30EF\u30A4\u30D7\u3067\u9589\u3058\u307E\u3059\u3002",
|
|
289
|
+
"settings.swipeHint": "\u5DE6\u7AEF\u304B\u3089\u53F3\u3078\u30B9\u30EF\u30A4\u30D7\u3067 Harness \u306E\u30CA\u30D3\u30B2\u30FC\u30B7\u30E7\u30F3\u30C9\u30ED\u30EF\u30FC\u3092\u958B\u304D\u3001\u5DE6\u3078\u30B9\u30EF\u30A4\u30D7\u3067\u9589\u3058\u307E\u3059\u3002",
|
|
383
290
|
"settings.fitKeyboard": "\u30AD\u30FC\u30DC\u30FC\u30C9\u306B\u5408\u308F\u305B\u3066\u30DA\u30FC\u30B8\u3092\u7E2E\u3081\u308B",
|
|
384
291
|
"settings.fitKeyboardHint": "\u30AA\u30F3\u30B9\u30AF\u30EA\u30FC\u30F3\u30AD\u30FC\u30DC\u30FC\u30C9\u306E\u8868\u793A\u4E2D\u3082\u3001\u30D8\u30C3\u30C0\u30FC\u3068\u5165\u529B\u6B04\u3092\u753B\u9762\u5185\u306B\u6B8B\u3057\u307E\u3059\u3002",
|
|
385
292
|
"settings.blockPullToRefresh": "\u5F15\u3063\u5F35\u3063\u3066\u66F4\u65B0\u3092\u6B62\u3081\u308B",
|
|
@@ -390,34 +297,34 @@ window.__ModuleLoader__.load({
|
|
|
390
297
|
"settings.writeError": "Host \u304C\u5909\u66F4\u3092\u62D2\u5426\u3057\u307E\u3057\u305F\u3002"
|
|
391
298
|
};
|
|
392
299
|
|
|
393
|
-
// src/client/
|
|
300
|
+
// src/client/page.ts
|
|
394
301
|
var TOUCH_QUERY = "(hover: none) and (pointer: coarse)";
|
|
395
|
-
function
|
|
396
|
-
return
|
|
302
|
+
function readPageState(drawer, touch) {
|
|
303
|
+
return { mobile: drawer, touch };
|
|
397
304
|
}
|
|
398
|
-
function
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
305
|
+
function mediaSource(matchMedia, query) {
|
|
306
|
+
const media = matchMedia(query);
|
|
307
|
+
return {
|
|
308
|
+
get: () => media.matches,
|
|
309
|
+
subscribe(listener) {
|
|
310
|
+
media.addEventListener("change", listener);
|
|
311
|
+
return () => {
|
|
312
|
+
media.removeEventListener("change", listener);
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
};
|
|
402
316
|
}
|
|
403
|
-
function
|
|
317
|
+
function createPageStore(signals) {
|
|
404
318
|
const listeners = /* @__PURE__ */ new Set();
|
|
405
|
-
const touch = matchMedia(TOUCH_QUERY);
|
|
406
|
-
let breakpoint = config().breakpoint;
|
|
407
|
-
let width = matchMedia(widthQuery(breakpoint));
|
|
408
319
|
let state = { mobile: false, touch: false };
|
|
409
320
|
const compute = () => {
|
|
410
|
-
const next =
|
|
411
|
-
mobile: resolveMobile(config().mode, width.matches),
|
|
412
|
-
touch: touch.matches
|
|
413
|
-
};
|
|
321
|
+
const next = readPageState(signals.drawer.get(), signals.touch.get());
|
|
414
322
|
if (next.mobile === state.mobile && next.touch === state.touch) return;
|
|
415
323
|
state = next;
|
|
416
324
|
for (const listener of [...listeners]) listener();
|
|
417
325
|
};
|
|
418
|
-
const
|
|
419
|
-
touch.
|
|
420
|
-
width.addEventListener("change", onChange);
|
|
326
|
+
const offDrawer = signals.drawer.subscribe(compute);
|
|
327
|
+
const offTouch = signals.touch.subscribe(compute);
|
|
421
328
|
compute();
|
|
422
329
|
return {
|
|
423
330
|
getSnapshot: () => state,
|
|
@@ -427,36 +334,21 @@ window.__ModuleLoader__.load({
|
|
|
427
334
|
listeners.delete(listener);
|
|
428
335
|
};
|
|
429
336
|
},
|
|
430
|
-
refresh() {
|
|
431
|
-
const next = config().breakpoint;
|
|
432
|
-
if (next !== breakpoint) {
|
|
433
|
-
width.removeEventListener("change", onChange);
|
|
434
|
-
breakpoint = next;
|
|
435
|
-
width = matchMedia(widthQuery(breakpoint));
|
|
436
|
-
width.addEventListener("change", onChange);
|
|
437
|
-
}
|
|
438
|
-
compute();
|
|
439
|
-
},
|
|
440
337
|
dispose() {
|
|
441
|
-
|
|
442
|
-
|
|
338
|
+
offDrawer();
|
|
339
|
+
offTouch();
|
|
340
|
+
signals.drawer.dispose?.();
|
|
341
|
+
signals.touch.dispose?.();
|
|
443
342
|
listeners.clear();
|
|
444
343
|
}
|
|
445
344
|
};
|
|
446
345
|
}
|
|
447
346
|
|
|
448
347
|
// src/client/SettingsPage.tsx
|
|
449
|
-
var
|
|
450
|
-
var
|
|
348
|
+
var import_react = require("react");
|
|
349
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
451
350
|
var FIELDS = Object.keys(DEFAULT_CONFIG);
|
|
452
|
-
var MODES2 = ["auto", "always", "off"];
|
|
453
351
|
var ENTER_KEYS2 = ["newline", "send"];
|
|
454
|
-
function parseBreakpoint(text) {
|
|
455
|
-
const trimmed = text.trim();
|
|
456
|
-
if (!/^\d+$/.test(trimmed)) return void 0;
|
|
457
|
-
const value = Number(trimmed);
|
|
458
|
-
return value >= MIN_BREAKPOINT && value <= MAX_BREAKPOINT ? value : void 0;
|
|
459
|
-
}
|
|
460
352
|
var sectionStyle = {
|
|
461
353
|
maxWidth: 680,
|
|
462
354
|
padding: "4px 0 32px",
|
|
@@ -510,17 +402,6 @@ window.__ModuleLoader__.load({
|
|
|
510
402
|
paddingTop: 4,
|
|
511
403
|
borderTop: "1px solid var(--dsw-alias-border-l2)"
|
|
512
404
|
};
|
|
513
|
-
var inputStyle = {
|
|
514
|
-
width: 96,
|
|
515
|
-
boxSizing: "border-box",
|
|
516
|
-
padding: "6px 10px",
|
|
517
|
-
borderRadius: 8,
|
|
518
|
-
border: "1px solid var(--dsw-alias-border-l2)",
|
|
519
|
-
background: "var(--dsw-specific-input-major, var(--dsw-alias-bg-layer-1))",
|
|
520
|
-
color: "inherit",
|
|
521
|
-
font: "inherit",
|
|
522
|
-
outline: "none"
|
|
523
|
-
};
|
|
524
405
|
var buttonStyle = {
|
|
525
406
|
marginTop: 26,
|
|
526
407
|
padding: "8px 12px",
|
|
@@ -536,118 +417,66 @@ window.__ModuleLoader__.load({
|
|
|
536
417
|
fontSize: 13,
|
|
537
418
|
lineHeight: 1.6
|
|
538
419
|
};
|
|
539
|
-
function
|
|
540
|
-
const subscribe = (0,
|
|
420
|
+
function useObservable(observable) {
|
|
421
|
+
const subscribe = (0, import_react.useCallback)(
|
|
541
422
|
(listener) => observable.subscribe(listener),
|
|
542
423
|
[observable]
|
|
543
424
|
);
|
|
544
|
-
const getSnapshot = (0,
|
|
545
|
-
return (0,
|
|
425
|
+
const getSnapshot = (0, import_react.useCallback)(() => observable.getSnapshot(), [observable]);
|
|
426
|
+
return (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
546
427
|
}
|
|
547
428
|
function subscribeResize(listener) {
|
|
548
429
|
window.addEventListener("resize", listener);
|
|
549
430
|
return () => window.removeEventListener("resize", listener);
|
|
550
431
|
}
|
|
551
432
|
function useViewportWidth() {
|
|
552
|
-
return (0,
|
|
433
|
+
return (0, import_react.useSyncExternalStore)(
|
|
553
434
|
subscribeResize,
|
|
554
435
|
() => Math.round(window.innerWidth),
|
|
555
436
|
() => 0
|
|
556
437
|
);
|
|
557
438
|
}
|
|
558
|
-
function SettingsPage({ form,
|
|
559
|
-
const subscribe = (0,
|
|
560
|
-
const getSnapshot = (0,
|
|
561
|
-
const snapshot = (0,
|
|
439
|
+
function SettingsPage({ form, page, t }) {
|
|
440
|
+
const subscribe = (0, import_react.useCallback)((listener) => form.subscribe(listener), [form]);
|
|
441
|
+
const getSnapshot = (0, import_react.useCallback)(() => form.getSnapshot(), [form]);
|
|
442
|
+
const snapshot = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
562
443
|
const config = normalizeConfig(snapshot.value);
|
|
563
|
-
const state =
|
|
444
|
+
const state = useObservable(page);
|
|
564
445
|
const width = useViewportWidth();
|
|
565
|
-
const [
|
|
566
|
-
const [writeError, setWriteError] = (0, import_react3.useState)(false);
|
|
567
|
-
(0, import_react3.useEffect)(() => {
|
|
568
|
-
setDraft(String(config.breakpoint));
|
|
569
|
-
}, [config.breakpoint]);
|
|
446
|
+
const [writeFailed, setWriteFailed] = (0, import_react.useState)(false);
|
|
570
447
|
const write = (field, value) => {
|
|
571
|
-
void form.set(field, value).then((accepted) =>
|
|
572
|
-
};
|
|
573
|
-
const parsed = parseBreakpoint(draft);
|
|
574
|
-
const commitBreakpoint = () => {
|
|
575
|
-
if (parsed === void 0) return;
|
|
576
|
-
if (parsed !== config.breakpoint) write("breakpoint", parsed);
|
|
577
|
-
};
|
|
578
|
-
const onBreakpointKeyDown = (event) => {
|
|
579
|
-
if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
|
|
580
|
-
event.preventDefault();
|
|
581
|
-
event.currentTarget.blur();
|
|
448
|
+
void form.set(field, value).then((accepted) => setWriteFailed(!accepted)).catch(() => setWriteFailed(true));
|
|
582
449
|
};
|
|
583
450
|
const reset = () => {
|
|
584
|
-
void Promise.all(FIELDS.map((field) => form.unset(field))).then((results) =>
|
|
451
|
+
void Promise.all(FIELDS.map((field) => form.unset(field))).then((results) => setWriteFailed(results.some((accepted) => !accepted))).catch(() => setWriteFailed(true));
|
|
585
452
|
};
|
|
586
453
|
if (snapshot.status === "unavailable") {
|
|
587
|
-
return /* @__PURE__ */ (0,
|
|
454
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: mutedStyle, children: t("settings.unavailable") });
|
|
588
455
|
}
|
|
589
456
|
const disabled = snapshot.writable === false;
|
|
590
|
-
return /* @__PURE__ */ (0,
|
|
591
|
-
/* @__PURE__ */ (0,
|
|
592
|
-
/* @__PURE__ */ (0,
|
|
593
|
-
|
|
594
|
-
/* @__PURE__ */ (0,
|
|
595
|
-
/* @__PURE__ */ (0,
|
|
596
|
-
/* @__PURE__ */ (0,
|
|
457
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: sectionStyle, className: "dsh-mobile-settings", children: [
|
|
458
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: headingStyle, children: t("settings.title") }),
|
|
459
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: introStyle, children: t("settings.description") }),
|
|
460
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: statusStyle, role: "status", children: state.mobile ? t("settings.statusMobile", { width }) : t("settings.statusDesktop", { width }) }),
|
|
461
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldStyle, children: [
|
|
462
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: labelStyle, children: t("settings.enterKey") }),
|
|
463
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
597
464
|
Segmented,
|
|
598
465
|
{
|
|
599
|
-
label: t("settings.
|
|
600
|
-
options:
|
|
601
|
-
|
|
466
|
+
label: t("settings.enterKey"),
|
|
467
|
+
options: ENTER_KEYS2.map((value) => ({
|
|
468
|
+
value,
|
|
469
|
+
label: t(`settings.enterKey.${value}`)
|
|
470
|
+
})),
|
|
471
|
+
value: config.enterKey,
|
|
602
472
|
disabled,
|
|
603
|
-
onChange: (next) => write("
|
|
473
|
+
onChange: (next) => write("enterKey", next)
|
|
604
474
|
}
|
|
605
475
|
),
|
|
606
|
-
/* @__PURE__ */ (0,
|
|
607
|
-
] }),
|
|
608
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: fieldStyle, children: [
|
|
609
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { style: labelStyle, htmlFor: "dsh-mobile-breakpoint", children: t("settings.breakpoint") }),
|
|
610
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
611
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
612
|
-
"input",
|
|
613
|
-
{
|
|
614
|
-
id: "dsh-mobile-breakpoint",
|
|
615
|
-
type: "text",
|
|
616
|
-
inputMode: "numeric",
|
|
617
|
-
autoComplete: "off",
|
|
618
|
-
spellCheck: false,
|
|
619
|
-
value: draft,
|
|
620
|
-
disabled,
|
|
621
|
-
"aria-invalid": parsed === void 0,
|
|
622
|
-
style: inputStyle,
|
|
623
|
-
onChange: (event) => setDraft(event.target.value),
|
|
624
|
-
onBlur: commitBreakpoint,
|
|
625
|
-
onKeyDown: onBreakpointKeyDown
|
|
626
|
-
}
|
|
627
|
-
),
|
|
628
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: mutedStyle, children: "px" })
|
|
629
|
-
] }),
|
|
630
|
-
parsed === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: errorStyle, children: t("settings.breakpointInvalid") }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: hintStyle, children: t("settings.breakpointHint") })
|
|
476
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: hintStyle, children: t("settings.enterKeyHint") })
|
|
631
477
|
] }),
|
|
632
|
-
/* @__PURE__ */ (0,
|
|
633
|
-
/* @__PURE__ */ (0,
|
|
634
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: labelStyle, children: t("settings.enterKey") }),
|
|
635
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
636
|
-
Segmented,
|
|
637
|
-
{
|
|
638
|
-
label: t("settings.enterKey"),
|
|
639
|
-
options: ENTER_KEYS2.map((value) => ({
|
|
640
|
-
value,
|
|
641
|
-
label: t(`settings.enterKey.${value}`)
|
|
642
|
-
})),
|
|
643
|
-
value: config.enterKey,
|
|
644
|
-
disabled,
|
|
645
|
-
onChange: (next) => write("enterKey", next)
|
|
646
|
-
}
|
|
647
|
-
),
|
|
648
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: hintStyle, children: t("settings.enterKeyHint") })
|
|
649
|
-
] }),
|
|
650
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
478
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: groupStyle, children: [
|
|
479
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
651
480
|
Toggle,
|
|
652
481
|
{
|
|
653
482
|
label: t("settings.autoFocusComposer"),
|
|
@@ -657,7 +486,7 @@ window.__ModuleLoader__.load({
|
|
|
657
486
|
onChange: (next) => write("autoFocusComposer", next)
|
|
658
487
|
}
|
|
659
488
|
),
|
|
660
|
-
/* @__PURE__ */ (0,
|
|
489
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
661
490
|
Toggle,
|
|
662
491
|
{
|
|
663
492
|
label: t("settings.swipe"),
|
|
@@ -667,7 +496,7 @@ window.__ModuleLoader__.load({
|
|
|
667
496
|
onChange: (next) => write("swipe", next)
|
|
668
497
|
}
|
|
669
498
|
),
|
|
670
|
-
/* @__PURE__ */ (0,
|
|
499
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
671
500
|
Toggle,
|
|
672
501
|
{
|
|
673
502
|
label: t("settings.fitKeyboard"),
|
|
@@ -677,7 +506,7 @@ window.__ModuleLoader__.load({
|
|
|
677
506
|
onChange: (next) => write("fitKeyboard", next)
|
|
678
507
|
}
|
|
679
508
|
),
|
|
680
|
-
/* @__PURE__ */ (0,
|
|
509
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
681
510
|
Toggle,
|
|
682
511
|
{
|
|
683
512
|
label: t("settings.blockPullToRefresh"),
|
|
@@ -688,9 +517,9 @@ window.__ModuleLoader__.load({
|
|
|
688
517
|
}
|
|
689
518
|
)
|
|
690
519
|
] }),
|
|
691
|
-
disabled ? /* @__PURE__ */ (0,
|
|
692
|
-
|
|
693
|
-
/* @__PURE__ */ (0,
|
|
520
|
+
disabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { ...hintStyle, marginTop: 20 }, children: t("settings.readOnly") }) : null,
|
|
521
|
+
writeFailed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { ...errorStyle, marginTop: 20 }, children: t("settings.writeError") }) : null,
|
|
522
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: buttonStyle, disabled, onClick: reset, children: t("settings.reset") })
|
|
694
523
|
] });
|
|
695
524
|
}
|
|
696
525
|
function Segmented({
|
|
@@ -700,7 +529,7 @@ window.__ModuleLoader__.load({
|
|
|
700
529
|
disabled,
|
|
701
530
|
onChange
|
|
702
531
|
}) {
|
|
703
|
-
return /* @__PURE__ */ (0,
|
|
532
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("fieldset", { className: "dsh-mobile-segmented", "aria-label": label, children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
704
533
|
"button",
|
|
705
534
|
{
|
|
706
535
|
type: "button",
|
|
@@ -716,12 +545,12 @@ window.__ModuleLoader__.load({
|
|
|
716
545
|
)) });
|
|
717
546
|
}
|
|
718
547
|
function Toggle({ label, hint, checked, disabled, onChange }) {
|
|
719
|
-
return /* @__PURE__ */ (0,
|
|
720
|
-
/* @__PURE__ */ (0,
|
|
721
|
-
/* @__PURE__ */ (0,
|
|
722
|
-
/* @__PURE__ */ (0,
|
|
548
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: rowStyle, children: [
|
|
549
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "flex", flexDirection: "column", gap: 4, minWidth: 0 }, children: [
|
|
550
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: labelStyle, children: label }),
|
|
551
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: hintStyle, children: hint })
|
|
723
552
|
] }),
|
|
724
|
-
/* @__PURE__ */ (0,
|
|
553
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
725
554
|
"button",
|
|
726
555
|
{
|
|
727
556
|
type: "button",
|
|
@@ -734,7 +563,7 @@ window.__ModuleLoader__.load({
|
|
|
734
563
|
event.stopPropagation();
|
|
735
564
|
onChange(!checked);
|
|
736
565
|
},
|
|
737
|
-
children: /* @__PURE__ */ (0,
|
|
566
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-mobile-thumb" })
|
|
738
567
|
}
|
|
739
568
|
)
|
|
740
569
|
] });
|
|
@@ -746,69 +575,8 @@ window.__ModuleLoader__.load({
|
|
|
746
575
|
var NO_PULL_ATTRIBUTE = "data-dsh-no-pull";
|
|
747
576
|
var M = `html[${MOBILE_ATTRIBUTE}]`;
|
|
748
577
|
var T = `html[${TOUCH_ATTRIBUTE}]`;
|
|
749
|
-
var SIDEBAR = `${M} ${FRAME} > :has(> [data-slot="sidebar"])`;
|
|
750
578
|
var SETTINGS = `${M} [data-shortcut-modal="settings"][role="dialog"]`;
|
|
751
579
|
var MOBILE_CSS = `
|
|
752
|
-
.dsh-mobile-menu {
|
|
753
|
-
box-sizing: border-box;
|
|
754
|
-
flex: none;
|
|
755
|
-
width: 34px;
|
|
756
|
-
height: 34px;
|
|
757
|
-
margin: -2px 2px -2px -4px;
|
|
758
|
-
padding: 0;
|
|
759
|
-
border: none;
|
|
760
|
-
border-radius: var(--dsw-radius-md);
|
|
761
|
-
background: transparent;
|
|
762
|
-
color: var(--dsw-alias-label-primary);
|
|
763
|
-
font-family: inherit;
|
|
764
|
-
display: inline-flex;
|
|
765
|
-
align-items: center;
|
|
766
|
-
justify-content: center;
|
|
767
|
-
cursor: pointer;
|
|
768
|
-
touch-action: manipulation;
|
|
769
|
-
-webkit-tap-highlight-color: transparent;
|
|
770
|
-
}
|
|
771
|
-
.dsh-mobile-menu[hidden] {
|
|
772
|
-
display: none;
|
|
773
|
-
}
|
|
774
|
-
.dsh-mobile-menu:active {
|
|
775
|
-
background: var(--dsw-alias-interactive-bg-hover);
|
|
776
|
-
}
|
|
777
|
-
@media (hover: hover) {
|
|
778
|
-
.dsh-mobile-menu:hover {
|
|
779
|
-
background: var(--dsw-alias-interactive-bg-hover);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
.dsh-mobile-menu:focus-visible {
|
|
783
|
-
outline: var(--dsw-focus-ring-width) solid
|
|
784
|
-
var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
785
|
-
outline-offset: -2px;
|
|
786
|
-
}
|
|
787
|
-
.dsh-mobile-floating {
|
|
788
|
-
position: absolute;
|
|
789
|
-
top: 8px;
|
|
790
|
-
left: 8px;
|
|
791
|
-
width: 40px;
|
|
792
|
-
height: 40px;
|
|
793
|
-
margin: 0;
|
|
794
|
-
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
795
|
-
border-radius: 999px;
|
|
796
|
-
background: var(--dsw-alias-button-elevated-fill, var(--dsw-alias-bg-layer-1));
|
|
797
|
-
box-shadow: var(--dsw-elevation-soft, 0 2px 8px rgb(0 0 0 / 12%));
|
|
798
|
-
}
|
|
799
|
-
.dsh-mobile-scrim {
|
|
800
|
-
position: absolute;
|
|
801
|
-
inset: 0;
|
|
802
|
-
background: var(--dsw-alias-bg-mask-1, rgb(0 0 0 / 40%));
|
|
803
|
-
opacity: 0;
|
|
804
|
-
pointer-events: none;
|
|
805
|
-
transition: opacity 0.2s ease;
|
|
806
|
-
-webkit-tap-highlight-color: transparent;
|
|
807
|
-
}
|
|
808
|
-
.dsh-mobile-scrim[data-open] {
|
|
809
|
-
opacity: 1;
|
|
810
|
-
pointer-events: auto;
|
|
811
|
-
}
|
|
812
580
|
.dsh-mobile-segmented {
|
|
813
581
|
box-sizing: border-box;
|
|
814
582
|
min-width: 0;
|
|
@@ -887,9 +655,6 @@ window.__ModuleLoader__.load({
|
|
|
887
655
|
transform: translate(16px);
|
|
888
656
|
}
|
|
889
657
|
|
|
890
|
-
${M} {
|
|
891
|
-
--dsh-frame-top-clearance: 44px;
|
|
892
|
-
}
|
|
893
658
|
${M} body {
|
|
894
659
|
height: var(--dsh-mobile-vvh, 100dvh);
|
|
895
660
|
overflow: hidden;
|
|
@@ -904,67 +669,17 @@ window.__ModuleLoader__.load({
|
|
|
904
669
|
overscroll-behavior: none;
|
|
905
670
|
}
|
|
906
671
|
|
|
907
|
-
${M} ${FRAME} {
|
|
908
|
-
grid-template-columns: 0px minmax(0, 1fr) 0px !important;
|
|
909
|
-
}
|
|
910
|
-
${M} ${FRAME} > :has(> [data-slot="main"]) {
|
|
911
|
-
grid-column: 2;
|
|
912
|
-
grid-row: 1;
|
|
913
|
-
}
|
|
914
|
-
${M} ${FRAME} > [data-rightbar-col] {
|
|
915
|
-
grid-column: 3;
|
|
916
|
-
grid-row: 1;
|
|
917
|
-
}
|
|
918
|
-
${M} ${FRAME} > [data-side] {
|
|
919
|
-
display: none;
|
|
920
|
-
}
|
|
921
|
-
${SIDEBAR} {
|
|
922
|
-
position: absolute;
|
|
923
|
-
z-index: 25;
|
|
924
|
-
top: 0;
|
|
925
|
-
bottom: 0;
|
|
926
|
-
left: 0;
|
|
927
|
-
box-sizing: border-box;
|
|
928
|
-
width: min(86vw, 340px);
|
|
929
|
-
padding-bottom: env(safe-area-inset-bottom);
|
|
930
|
-
border-right: none;
|
|
931
|
-
box-shadow: var(--dsw-elevation-prominent, 0 8px 32px rgb(0 0 0 / 20%));
|
|
932
|
-
overscroll-behavior: contain;
|
|
933
|
-
transition: transform 0.24s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
934
|
-
}
|
|
935
|
-
${M} ${FRAME}[data-sidebar-collapsed] > :has(> [data-slot="sidebar"]) {
|
|
936
|
-
transform: translateX(calc(-100% - 32px));
|
|
937
|
-
visibility: hidden;
|
|
938
|
-
box-shadow: none;
|
|
939
|
-
transition:
|
|
940
|
-
transform 0.2s cubic-bezier(0.4, 0, 1, 1),
|
|
941
|
-
visibility 0s linear 0.2s,
|
|
942
|
-
box-shadow 0s linear 0.2s;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
${M} header:has(> [data-conversation-header-leading]) {
|
|
946
|
-
min-height: 0;
|
|
947
|
-
padding-top: 6px;
|
|
948
|
-
padding-left: 8px;
|
|
949
|
-
padding-right: 12px;
|
|
950
|
-
}
|
|
951
|
-
${M} [data-conversation-header-corner] {
|
|
952
|
-
margin-right: 0;
|
|
953
|
-
}
|
|
954
|
-
${M} [data-conversation-tabs] {
|
|
955
|
-
gap: 20px;
|
|
956
|
-
}
|
|
957
672
|
${M} [data-conversation-content] {
|
|
958
673
|
--dsh-composer-side-clearance: 8px;
|
|
959
674
|
}
|
|
960
|
-
${M} [data-
|
|
961
|
-
|
|
675
|
+
${M} div[data-sidebar-drawer] > :has(> [data-slot="sidebar"]) {
|
|
676
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
962
677
|
}
|
|
963
678
|
${M} [data-composer-seat] {
|
|
964
679
|
--dsh-composer-text-max-height: min(336px, calc(var(--dsh-mobile-vvh, 100dvh) * 0.3));
|
|
965
680
|
padding-bottom: env(safe-area-inset-bottom);
|
|
966
681
|
}
|
|
967
|
-
|
|
682
|
+
${M}[data-dsh-keyboard] [data-composer-seat] {
|
|
968
683
|
padding-bottom: 0;
|
|
969
684
|
}
|
|
970
685
|
|
|
@@ -1056,9 +771,8 @@ window.__ModuleLoader__.load({
|
|
|
1056
771
|
}
|
|
1057
772
|
|
|
1058
773
|
@media (prefers-reduced-motion: reduce) {
|
|
1059
|
-
.dsh-mobile-
|
|
1060
|
-
|
|
1061
|
-
transition: none !important;
|
|
774
|
+
.dsh-mobile-thumb {
|
|
775
|
+
transition: none;
|
|
1062
776
|
}
|
|
1063
777
|
}
|
|
1064
778
|
`;
|
|
@@ -1253,11 +967,9 @@ window.__ModuleLoader__.load({
|
|
|
1253
967
|
}
|
|
1254
968
|
|
|
1255
969
|
// src/client/index.tsx
|
|
1256
|
-
var
|
|
970
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1257
971
|
var inject = ["slots", "locale", "configForms", "layout"];
|
|
1258
972
|
var PLUGIN_ID = "@1outres/dsh-plugin-mobile";
|
|
1259
|
-
var HEADER_SLOT = "conversation.header.leading";
|
|
1260
|
-
var OVERLAY_SLOT = "shell.overlay";
|
|
1261
973
|
var SETTINGS_SLOT = "settings.section";
|
|
1262
974
|
function apply(ctx) {
|
|
1263
975
|
ctx.effect(() => ctx.locale.register(NAMESPACE, { en, zh, ja }), "mobile: dictionaries");
|
|
@@ -1275,18 +987,18 @@ window.__ModuleLoader__.load({
|
|
|
1275
987
|
ctx.effect(() => installMeta(document), "mobile: viewport metadata");
|
|
1276
988
|
const form = ctx.configForms.get(NAMESPACE);
|
|
1277
989
|
let config = normalizeConfig(form.getSnapshot().value);
|
|
1278
|
-
const
|
|
1279
|
-
(
|
|
1280
|
-
() =>
|
|
1281
|
-
);
|
|
1282
|
-
ctx.effect(() => () =>
|
|
990
|
+
const page = createPageStore({
|
|
991
|
+
drawer: createDrawerSource(document, observeMutations),
|
|
992
|
+
touch: mediaSource((query) => window.matchMedia(query), TOUCH_QUERY)
|
|
993
|
+
});
|
|
994
|
+
ctx.effect(() => () => page.dispose(), "mobile: page state");
|
|
1283
995
|
ctx.effect(() => {
|
|
1284
996
|
const root = document.documentElement;
|
|
1285
997
|
let stopViewport;
|
|
1286
998
|
const sync = () => {
|
|
1287
|
-
const state =
|
|
999
|
+
const state = page.getSnapshot();
|
|
1288
1000
|
root.toggleAttribute(MOBILE_ATTRIBUTE, state.mobile);
|
|
1289
|
-
root.toggleAttribute(TOUCH_ATTRIBUTE, state.touch
|
|
1001
|
+
root.toggleAttribute(TOUCH_ATTRIBUTE, state.touch);
|
|
1290
1002
|
root.toggleAttribute(NO_PULL_ATTRIBUTE, state.mobile && config.blockPullToRefresh);
|
|
1291
1003
|
const fit = state.mobile && config.fitKeyboard;
|
|
1292
1004
|
if (fit && stopViewport === void 0) {
|
|
@@ -1296,15 +1008,14 @@ window.__ModuleLoader__.load({
|
|
|
1296
1008
|
stopViewport = void 0;
|
|
1297
1009
|
}
|
|
1298
1010
|
};
|
|
1299
|
-
const
|
|
1011
|
+
const offPage = page.subscribe(sync);
|
|
1300
1012
|
const offForm = form.subscribe(() => {
|
|
1301
1013
|
config = normalizeConfig(form.getSnapshot().value);
|
|
1302
|
-
mode.refresh();
|
|
1303
1014
|
sync();
|
|
1304
1015
|
});
|
|
1305
1016
|
sync();
|
|
1306
1017
|
return () => {
|
|
1307
|
-
|
|
1018
|
+
offPage();
|
|
1308
1019
|
offForm();
|
|
1309
1020
|
stopViewport?.();
|
|
1310
1021
|
root.removeAttribute(MOBILE_ATTRIBUTE);
|
|
@@ -1319,24 +1030,23 @@ window.__ModuleLoader__.load({
|
|
|
1319
1030
|
console.warn("mobile: could not toggle the sidebar", error);
|
|
1320
1031
|
}
|
|
1321
1032
|
};
|
|
1322
|
-
const face = { mode, toggle };
|
|
1323
1033
|
ctx.effect(
|
|
1324
1034
|
() => installEnterNewline(window, () => {
|
|
1325
|
-
const state =
|
|
1035
|
+
const state = page.getSnapshot();
|
|
1326
1036
|
return state.mobile && state.touch && config.enterKey === "newline";
|
|
1327
1037
|
}),
|
|
1328
1038
|
"mobile: Enter adds a line"
|
|
1329
1039
|
);
|
|
1330
1040
|
ctx.effect(
|
|
1331
1041
|
() => installFocusGuard(document, () => {
|
|
1332
|
-
const state =
|
|
1042
|
+
const state = page.getSnapshot();
|
|
1333
1043
|
return state.mobile && state.touch && !config.autoFocusComposer;
|
|
1334
1044
|
}),
|
|
1335
1045
|
"mobile: keyboard stays closed until the composer is tapped"
|
|
1336
1046
|
);
|
|
1337
1047
|
ctx.effect(
|
|
1338
1048
|
() => installSwipe(document, {
|
|
1339
|
-
enabled: () =>
|
|
1049
|
+
enabled: () => page.getSnapshot().mobile && config.swipe,
|
|
1340
1050
|
expanded: () => isExpanded(findFrame(document)),
|
|
1341
1051
|
blocked: (target) => swipeBlocked(
|
|
1342
1052
|
target,
|
|
@@ -1347,27 +1057,7 @@ window.__ModuleLoader__.load({
|
|
|
1347
1057
|
}),
|
|
1348
1058
|
"mobile: drawer swipe"
|
|
1349
1059
|
);
|
|
1350
|
-
|
|
1351
|
-
() => ctx.slots.inject(
|
|
1352
|
-
HEADER_SLOT,
|
|
1353
|
-
() => ctx.slots.register(
|
|
1354
|
-
{ name: HEADER_SLOT, locale: NAMESPACE, inject: () => face },
|
|
1355
|
-
HeaderMenuButton
|
|
1356
|
-
)
|
|
1357
|
-
),
|
|
1358
|
-
"mobile: header menu button"
|
|
1359
|
-
);
|
|
1360
|
-
ctx.effect(
|
|
1361
|
-
() => ctx.slots.inject(
|
|
1362
|
-
OVERLAY_SLOT,
|
|
1363
|
-
() => ctx.slots.register(
|
|
1364
|
-
{ name: OVERLAY_SLOT, id: "mobile-drawer", locale: NAMESPACE, inject: () => face },
|
|
1365
|
-
DrawerOverlay
|
|
1366
|
-
)
|
|
1367
|
-
),
|
|
1368
|
-
"mobile: drawer scrim"
|
|
1369
|
-
);
|
|
1370
|
-
const Page = () => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SettingsPage, { form, mode, t });
|
|
1060
|
+
const Page = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SettingsPage, { form, page, t });
|
|
1371
1061
|
ctx.effect(
|
|
1372
1062
|
() => ctx.configForms.whileServed(
|
|
1373
1063
|
[NAMESPACE],
|
package/lib/index.js
CHANGED
|
@@ -2,20 +2,18 @@
|
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
var name = "mobile";
|
|
4
4
|
var Config = z.object({
|
|
5
|
-
mode: z.union(["auto", "always", "off"]).default("auto").description(
|
|
6
|
-
"When to use the phone layout: auto below the breakpoint width, always, or never (off)."
|
|
7
|
-
).volatile(),
|
|
8
|
-
breakpoint: z.natural().min(360).max(1024).default(768).description("Viewport width in CSS pixels below which auto mode uses the phone layout.").volatile(),
|
|
9
5
|
enterKey: z.union(["newline", "send"]).default("newline").description(
|
|
10
6
|
"What Enter does in the composer on a touch screen in the phone layout: add a line (send with the button) or send."
|
|
11
7
|
).volatile(),
|
|
12
|
-
swipe: z.boolean().default(true).description(
|
|
8
|
+
swipe: z.boolean().default(true).description(
|
|
9
|
+
"Open the Harness navigation drawer by swiping right from the left edge, close it by swiping left."
|
|
10
|
+
).volatile(),
|
|
13
11
|
fitKeyboard: z.boolean().default(true).description(
|
|
14
12
|
"Fit the page to the area the on-screen keyboard leaves visible, so the header and the composer stay on screen."
|
|
15
13
|
).volatile(),
|
|
16
14
|
blockPullToRefresh: z.boolean().default(true).description("Stop pull-to-refresh and overscroll bounce from reloading or moving the page.").volatile(),
|
|
17
15
|
autoFocusComposer: z.boolean().default(false).description(
|
|
18
|
-
"Let the
|
|
16
|
+
"Let the Harness focus the composer on a touch screen in the phone layout, as on desktop, when a composer control or another action moves focus to it. Off keeps the on-screen keyboard closed until you tap the input."
|
|
19
17
|
).volatile()
|
|
20
18
|
});
|
|
21
19
|
function apply(ctx) {
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "Mobile",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Adds phone refinements to the Harness Web UI: fits the page to the on-screen keyboard, makes Enter add a line, keeps the keyboard closed until the composer is tapped, adds edge swipes to the Harness navigation drawer, and respects notches and touch."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/ja.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "モバイル",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "Harness の Web UI にスマートフォン向けの調整を足します。オンスクリーンキーボードの上に収まるようページを縮め、Enter で改行し、入力欄をタップするまでキーボードを開かず、Harness のナビゲーションドロワーを画面端のスワイプで開閉できるようにします。ノッチやタッチ操作にも合わせます。"
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "移动端",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "在 Harness 的 Web UI 上补充手机体验:页面适配屏幕键盘上方的可见区域,Enter 换行,点按输入框之前不弹出键盘,屏幕端部的滑动可开闭 Harness 的导航抽屉,并避开刘海等安全区域。"
|
|
5
5
|
}
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1outres/dsh-plugin-mobile",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Adds phone refinements to the DeepSeek Harness Web UI: fits the page to the on-screen keyboard, makes Enter add a line, keeps the keyboard closed until the composer is tapped, adds edge swipes to the Harness navigation drawer, and respects notches and touch.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/1outres/my-dsh-plugins.git",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
|
-
"dsh": ">=0.2.
|
|
37
|
+
"dsh": ">=0.2.2-rc.1 <0.3.0",
|
|
38
38
|
"node": ">=22"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@deepseek-ai/schemastery": "~3.18.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@deepseek-ai/cordis": "~4.0
|
|
44
|
+
"@deepseek-ai/cordis": "~4.1.0",
|
|
45
45
|
"react": "^18.2.0"
|
|
46
46
|
},
|
|
47
47
|
"dsh": {
|