dsh-hide-ui 0.0.0-stage → 0.4.4
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/LICENSE +21 -0
- package/README.md +133 -2
- package/client.js +912 -0
- package/cordis.patch.yml +5 -0
- package/icon.svg +6 -0
- package/index.js +7 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +37 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-hide-ui contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,134 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-hide-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A DeepSeek Harness UI plugin that hides the interface regions you choose on
|
|
4
|
+
right-click, so the wallpaper behind them is visible.
|
|
5
|
+
|
|
6
|
+
## Choose what to hide
|
|
7
|
+
|
|
8
|
+
`Ctrl` + right-click anywhere opens the surface picker. Toggle any combination:
|
|
9
|
+
|
|
10
|
+
- **Sidebar** - sessions, search, and navigation.
|
|
11
|
+
- **Cards & panels** - menus, popovers, and option panels.
|
|
12
|
+
- **Input & controls** - composer, input bar, and takeover controls.
|
|
13
|
+
- **Conversation text frame** - the message list and scrollable text flow.
|
|
14
|
+
- **Trajectory view** - the trajectory main view, when active.
|
|
15
|
+
- **Produced / highlights** - code blocks, tool cards, diffs, and changed files.
|
|
16
|
+
- **Header popovers** - dropdown menus opened from the header.
|
|
17
|
+
- **Header bar** - the conversation title row and its controls.
|
|
18
|
+
- **Window menu** - the Application and Edit buttons in the desktop title bar.
|
|
19
|
+
- **Right sidebar** - trajectory panel and side panels, when open.
|
|
20
|
+
- **Entire window content** - the whole app shell plus the desktop window menu; overrides every surface above.
|
|
21
|
+
|
|
22
|
+
A surface that is not currently mounted (a closed right sidebar, an inactive
|
|
23
|
+
trajectory view, a closed menu) simply has nothing to hide until it appears.
|
|
24
|
+
|
|
25
|
+
Each target is a card with a toggle switch. The choice is stored in
|
|
26
|
+
`localStorage` under `dsh-hide-ui:targets` and reused on the next start. Turn
|
|
27
|
+
every target off to disable the gesture. The same toggle list is registered as a
|
|
28
|
+
**Hide UI** section in the host Settings dialog, and both surfaces write the same
|
|
29
|
+
selection.
|
|
30
|
+
|
|
31
|
+
## Gestures
|
|
32
|
+
|
|
33
|
+
| Gesture | Result |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `Ctrl` + right-click (anywhere) | Open or close the target picker |
|
|
36
|
+
| Right-click the conversation column or any selected surface | Hide the selected surfaces |
|
|
37
|
+
| **Hide now** in the picker | Apply the current selection immediately and close the picker |
|
|
38
|
+
| **Restore** in the picker (while hidden) | Restore every surface |
|
|
39
|
+
| Right-click while hidden | Restore; the right-click is swallowed |
|
|
40
|
+
| Left-click while hidden | Restore; the click is swallowed |
|
|
41
|
+
| `Esc` while hidden | Restore |
|
|
42
|
+
| `Esc` with the picker open | Close the picker |
|
|
43
|
+
| Right-click outside the selected regions (while visible) | Left alone, so host context menus keep working |
|
|
44
|
+
| `Shift` + right-click | Left alone, for native menu and text-selection paths |
|
|
45
|
+
|
|
46
|
+
Hiding blurs a focused composer that sits inside a hidden region, so keystrokes
|
|
47
|
+
cannot land in an invisible input; restoring returns focus. Regions are hidden
|
|
48
|
+
with `visibility` and `opacity` and are not unmounted, so their state, scroll
|
|
49
|
+
position, and layout survive.
|
|
50
|
+
|
|
51
|
+
## How it works
|
|
52
|
+
|
|
53
|
+
`client.js` is a plain-JS browser module registered through
|
|
54
|
+
`window.__ModuleLoader__`; there is no build step and no runtime dependency.
|
|
55
|
+
|
|
56
|
+
- The layout columns are discovered structurally, the way `dsh-any-background`
|
|
57
|
+
does it: the frame is the parent of `[data-shell-overlay]`, and its children
|
|
58
|
+
are `[sidebar, center, rightbar?, overlay]`. `data-rightbar-col` marks the
|
|
59
|
+
right column, so the left and centre columns are read by position around it.
|
|
60
|
+
CSS-module suffixes (`_centerCol`, `_sidebarCol`, `_frame`) remain only as a
|
|
61
|
+
fallback when that structure is missing.
|
|
62
|
+
- The nine surface groups reuse `dsh-any-background`'s stable host markers:
|
|
63
|
+
`[data-conversation-composer-overlay]` for the trajectory view,
|
|
64
|
+
`[data-composer-card]`/`[data-cordis-panel]` for input, the produced set
|
|
65
|
+
(`[data-code-block-content]`, `[data-diff]`, `[data-changed-files]`, ...),
|
|
66
|
+
and the header-popover set (`[role="menu"]`, `[data-menu-material]`, ...).
|
|
67
|
+
The header bar resolves from `[data-slot^="conversation.session.header"]`.
|
|
68
|
+
- The conversation target is `[data-chat-flow]`, not the centre column, so the
|
|
69
|
+
header bar and composer keep their own independent toggles. The shell target
|
|
70
|
+
is the frame itself.
|
|
71
|
+
- The selected elements get `.dsh-hide-ui-target`; the shell frame gets
|
|
72
|
+
`.dsh-hide-ui-frame`. One injected stylesheet hides marked targets while
|
|
73
|
+
`data-dsh-hide-ui="on"` is set on `<html>`.
|
|
74
|
+
- The shell frame normally paints a shell-wide fill. The plugin clears it for
|
|
75
|
+
region mode only, so the body-level wallpaper shows through the vacated
|
|
76
|
+
tracks. In whole-shell mode the window itself is hidden, so the fill is left
|
|
77
|
+
alone.
|
|
78
|
+
- Listeners are capture-phase, before the host's React handlers, and are
|
|
79
|
+
removed through `ctx.effect` on unload.
|
|
80
|
+
- The settings section registers through
|
|
81
|
+
`ctx.slots.inject('settings.section', ...)` with id `dsh-hide-ui`, order 70,
|
|
82
|
+
and locale namespace `settings.hideUi`.
|
|
83
|
+
|
|
84
|
+
## Install
|
|
85
|
+
|
|
86
|
+
Copy or clone this directory anywhere, then link it into the `desktop` profile
|
|
87
|
+
(the plugin is referenced in place, not copied). `dsh plugin add` records it in
|
|
88
|
+
`dsh.profile.bundles`.
|
|
89
|
+
|
|
90
|
+
A running DSH window mounts its client bundles once at startup. After editing
|
|
91
|
+
`client.js`, reinstall if needed, then **restart the app** to load the new
|
|
92
|
+
bundle. Reloading only the renderer can leave the plugin unmounted.
|
|
93
|
+
|
|
94
|
+
Preferred: install the directory through Plugin Manager's *install bundle*
|
|
95
|
+
action with the absolute package directory as the target.
|
|
96
|
+
|
|
97
|
+
Manual fallback:
|
|
98
|
+
|
|
99
|
+
```powershell
|
|
100
|
+
dsh plugin --profile desktop add "link:<path-to-dsh-hide-ui>"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Uninstall
|
|
104
|
+
|
|
105
|
+
Remove `"dsh-hide-ui"` from `dsh.profile.bundles`, then run:
|
|
106
|
+
|
|
107
|
+
```powershell
|
|
108
|
+
dsh plugin --profile desktop remove dsh-hide-ui
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Limits
|
|
112
|
+
|
|
113
|
+
- Region resolution depends on the shell structure (`[data-shell-overlay]`,
|
|
114
|
+
`data-rightbar-col`) and falls back to the `_centerCol`/`_sidebarCol` class
|
|
115
|
+
suffixes. A host that changes both breaks the affected target only.
|
|
116
|
+
- The Settings section expects the host's store spec shape (`create()`); if a
|
|
117
|
+
host build rejects it, the Ctrl+right-click picker still works.
|
|
118
|
+
- The right sidebar exists only while open. Selecting it while closed hides the
|
|
119
|
+
other selected regions; the right sidebar joins the next hide once it is
|
|
120
|
+
open.
|
|
121
|
+
- Whole-window mode hides every shell region, including both sidebars, and
|
|
122
|
+
overrides the region checkboxes while checked.
|
|
123
|
+
- The wallpaper must be a body-level layer behind the shell frame, as painted
|
|
124
|
+
by appearance plugins such as `dsh-any-background`.
|
|
125
|
+
- Hiding uses `visibility`, so a hidden region still occupies its grid track;
|
|
126
|
+
the cleared frame fill is what reveals the wallpaper.
|
|
127
|
+
- If the host cannot resolve `react`, the Settings section is disabled; the
|
|
128
|
+
picker and the gesture keep working.
|
|
129
|
+
|
|
130
|
+
## Compatibility
|
|
131
|
+
|
|
132
|
+
DSH Web Client on the `0.2.x` line. The client half uses only the module loader
|
|
133
|
+
contract (`window.__ModuleLoader__.load`), Cordis `ctx.effect`, `ctx.slots`,
|
|
134
|
+
`ctx.locale`, and DOM APIs; it imports no Harness client package.
|
package/client.js
ADDED
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: 'dsh-hide-ui',
|
|
3
|
+
factory(require) {
|
|
4
|
+
/** Attribute mirrored onto <html> while the selected regions are hidden. */
|
|
5
|
+
const ACTIVE_ATTR = 'data-dsh-hide-ui';
|
|
6
|
+
/** Marker placed on every element that is hidden while active. */
|
|
7
|
+
const TARGET_CLASS = 'dsh-hide-ui-target';
|
|
8
|
+
/** Marker placed on the shell frame so its fill does not cover the wallpaper. */
|
|
9
|
+
const FRAME_CLASS = 'dsh-hide-ui-frame';
|
|
10
|
+
const STYLE_ID = 'dsh-hide-ui-style';
|
|
11
|
+
const STORAGE_KEY = 'dsh-hide-ui:targets';
|
|
12
|
+
const NS = 'settings.hideUi';
|
|
13
|
+
const REGION_IDS = [
|
|
14
|
+
'sidebar',
|
|
15
|
+
'cards',
|
|
16
|
+
'input',
|
|
17
|
+
'chat',
|
|
18
|
+
'trajectory',
|
|
19
|
+
'produced',
|
|
20
|
+
'header',
|
|
21
|
+
'headerBar',
|
|
22
|
+
'windowMenu',
|
|
23
|
+
'rightbar',
|
|
24
|
+
];
|
|
25
|
+
const DEFAULT_SELECTION = { shell: false, regions: ['chat'] };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Selector sets borrowed from dsh-any-background's surface model. The
|
|
29
|
+
* structural targets (sidebar, chat column, right sidebar, shell) and the
|
|
30
|
+
* header bar are resolved separately; everything else matches stable host
|
|
31
|
+
* data-* markers rather than CSS-module class names.
|
|
32
|
+
*/
|
|
33
|
+
const TARGET_SELECTORS = {
|
|
34
|
+
cards: [
|
|
35
|
+
'[role="menu"]:not([data-dockkit-tab-menu]):not(.dsh-hide-ui-config)',
|
|
36
|
+
'[data-menu-material]:not([data-dockkit-tab-menu])',
|
|
37
|
+
'[role="listbox"]',
|
|
38
|
+
'body > [role="tree"]',
|
|
39
|
+
'body > [role="dialog"]:not([aria-modal="true"]):not(.dsh-hide-ui-config)',
|
|
40
|
+
],
|
|
41
|
+
input: [
|
|
42
|
+
'[data-composer-card]',
|
|
43
|
+
'[data-composer-seat]',
|
|
44
|
+
'[data-cordis-panel]',
|
|
45
|
+
'[data-approval-key] > div',
|
|
46
|
+
'[data-plan-review-key] > section',
|
|
47
|
+
'[data-question-key] > section',
|
|
48
|
+
],
|
|
49
|
+
trajectory: ['[data-conversation-composer-overlay]'],
|
|
50
|
+
produced: [
|
|
51
|
+
'[data-code-block-content]',
|
|
52
|
+
'[data-code-block-banner]',
|
|
53
|
+
'[data-composer-chip]',
|
|
54
|
+
'[data-changed-files]',
|
|
55
|
+
'[data-terminal]',
|
|
56
|
+
'[data-read]',
|
|
57
|
+
'[data-diff]',
|
|
58
|
+
'[data-context-injection-body]',
|
|
59
|
+
'[data-search]',
|
|
60
|
+
'[data-web]',
|
|
61
|
+
'[class*="_ioCard"]',
|
|
62
|
+
'[data-inspect]',
|
|
63
|
+
'[data-turn-trigger]',
|
|
64
|
+
],
|
|
65
|
+
windowMenu: ['[data-windows-menu]'],
|
|
66
|
+
header: [
|
|
67
|
+
'[data-dsh-any-header-popover]',
|
|
68
|
+
'ul[class*="_menu"]',
|
|
69
|
+
'div[role="dialog"][class*="_panel"]:not([aria-modal="true"])',
|
|
70
|
+
'[role="menu"][class*="_denseList"]:not([class*="_portal"])',
|
|
71
|
+
'[role="tree"][class*="_menu"]',
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
let React = null;
|
|
76
|
+
try {
|
|
77
|
+
React = require('react');
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.warn('dsh-hide-ui: react is unavailable; the settings section will be disabled', error);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const en = {
|
|
83
|
+
nav: 'Hide UI',
|
|
84
|
+
desc: 'Choose which surfaces the right-click gesture hides. Right-click a selected surface to hide it; right-click again, click, or press Esc to restore.',
|
|
85
|
+
sidebar: 'Sidebar',
|
|
86
|
+
sidebarHint: 'Sessions, search, and navigation.',
|
|
87
|
+
cards: 'Cards & panels',
|
|
88
|
+
cardsHint: 'Menus, popovers, and option panels.',
|
|
89
|
+
input: 'Input & controls',
|
|
90
|
+
inputHint: 'Composer, input bar, and takeover controls.',
|
|
91
|
+
chat: 'Conversation text frame',
|
|
92
|
+
chatHint: 'Messages and the scrollable text flow.',
|
|
93
|
+
trajectory: 'Trajectory view',
|
|
94
|
+
trajectoryHint: 'The trajectory main view, when active.',
|
|
95
|
+
produced: 'Produced / highlights',
|
|
96
|
+
producedHint: 'Code blocks, tool cards, diffs, and changed files.',
|
|
97
|
+
header: 'Header popovers',
|
|
98
|
+
headerHint: 'Dropdown menus opened from the header.',
|
|
99
|
+
headerBar: 'Header bar',
|
|
100
|
+
headerBarHint: 'The conversation title row and its controls.',
|
|
101
|
+
windowMenu: 'Window menu',
|
|
102
|
+
windowMenuHint: 'Application and Edit buttons in the desktop title bar.',
|
|
103
|
+
rightbar: 'Right sidebar',
|
|
104
|
+
rightbarHint: 'Trajectory panel and side panels, when open.',
|
|
105
|
+
shell: 'Entire window content',
|
|
106
|
+
shellHint: 'Hides the whole app shell. Overrides every surface above.',
|
|
107
|
+
empty: 'Select at least one surface, or the gesture stays disabled.',
|
|
108
|
+
hideNow: 'Hide now',
|
|
109
|
+
restore: 'Restore',
|
|
110
|
+
reset: 'Reset to defaults',
|
|
111
|
+
close: 'Close',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const zh = {
|
|
115
|
+
nav: '隐藏界面',
|
|
116
|
+
desc: '选择右键手势要隐藏的界面区域。在选中的区域内右键即可隐藏;再次右键、点击或按 Esc 恢复。',
|
|
117
|
+
sidebar: '左侧边栏',
|
|
118
|
+
sidebarHint: '会话列表、搜索和导航。',
|
|
119
|
+
cards: '卡片与面板',
|
|
120
|
+
cardsHint: '菜单、浮层和选项面板。',
|
|
121
|
+
input: '输入框与控件',
|
|
122
|
+
inputHint: '输入框、输入栏和接管控件。',
|
|
123
|
+
chat: '对话文本框',
|
|
124
|
+
chatHint: '消息列表和可滚动的文本区域。',
|
|
125
|
+
trajectory: '轨迹页',
|
|
126
|
+
trajectoryHint: '轨迹主视图(打开时)。',
|
|
127
|
+
produced: '产出物 / 高亮内容',
|
|
128
|
+
producedHint: '代码块、工具卡片、差异和变更文件。',
|
|
129
|
+
header: '顶栏选项',
|
|
130
|
+
headerHint: '从顶栏打开的菜单。',
|
|
131
|
+
headerBar: '标题栏',
|
|
132
|
+
headerBarHint: '会话标题行及其控件。',
|
|
133
|
+
windowMenu: '窗口菜单',
|
|
134
|
+
windowMenuHint: '桌面标题栏中的“应用”和“编辑”按钮。',
|
|
135
|
+
rightbar: '右侧边栏',
|
|
136
|
+
rightbarHint: '轨迹面板和侧面板(打开时)。',
|
|
137
|
+
shell: '整个窗口内容',
|
|
138
|
+
shellHint: '隐藏整个应用外壳,并覆盖上面的所有区域。',
|
|
139
|
+
empty: '至少选择一个区域,否则手势不会生效。',
|
|
140
|
+
hideNow: '立即隐藏',
|
|
141
|
+
restore: '恢复',
|
|
142
|
+
reset: '恢复默认',
|
|
143
|
+
close: '关闭',
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Selected targets are hidden with visibility/opacity; the shell frame fill
|
|
148
|
+
* is cleared so the body-level wallpaper shows through vacated grid tracks.
|
|
149
|
+
*/
|
|
150
|
+
const CSS = `
|
|
151
|
+
.dsh-hide-ui-config {
|
|
152
|
+
position: fixed;
|
|
153
|
+
left: 50%;
|
|
154
|
+
top: 50%;
|
|
155
|
+
transform: translate(-50%, -50%);
|
|
156
|
+
z-index: 2147483000;
|
|
157
|
+
box-sizing: border-box;
|
|
158
|
+
min-width: 300px;
|
|
159
|
+
max-width: 340px;
|
|
160
|
+
max-height: calc(100vh - 64px);
|
|
161
|
+
overflow-y: auto;
|
|
162
|
+
padding: 12px;
|
|
163
|
+
border-radius: 10px;
|
|
164
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, .35));
|
|
165
|
+
background: var(--dsw-alias-bg-layer-2, #fff);
|
|
166
|
+
color: var(--dsw-alias-label-primary, #111);
|
|
167
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, .22);
|
|
168
|
+
font: 13px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
169
|
+
}
|
|
170
|
+
.dsh-hide-ui-config-title {
|
|
171
|
+
margin: 0 0 8px;
|
|
172
|
+
font-size: 13px;
|
|
173
|
+
font-weight: 600;
|
|
174
|
+
}
|
|
175
|
+
.dsh-hide-ui-config-row {
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-direction: row-reverse;
|
|
178
|
+
align-items: center;
|
|
179
|
+
justify-content: space-between;
|
|
180
|
+
gap: 10px;
|
|
181
|
+
margin: 4px 0;
|
|
182
|
+
padding: 8px 10px;
|
|
183
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, .28));
|
|
184
|
+
border-radius: 8px;
|
|
185
|
+
background: var(--dsw-alias-bg-layer-1, rgba(127, 127, 127, .04));
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
}
|
|
188
|
+
.dsh-hide-ui-config-row:has(input:checked) {
|
|
189
|
+
border-color: var(--dsw-alias-brand-primary, #4d6bfe);
|
|
190
|
+
}
|
|
191
|
+
.dsh-hide-ui-config-row input {
|
|
192
|
+
appearance: none;
|
|
193
|
+
-webkit-appearance: none;
|
|
194
|
+
position: relative;
|
|
195
|
+
flex: none;
|
|
196
|
+
width: 38px;
|
|
197
|
+
height: 22px;
|
|
198
|
+
margin: 0;
|
|
199
|
+
border-radius: 99px;
|
|
200
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, .45));
|
|
201
|
+
background: var(--dsw-alias-bg-layer-2, #e5e5e5);
|
|
202
|
+
cursor: pointer;
|
|
203
|
+
transition: background .2s ease, border-color .2s ease;
|
|
204
|
+
}
|
|
205
|
+
.dsh-hide-ui-config-row input::before {
|
|
206
|
+
content: "";
|
|
207
|
+
position: absolute;
|
|
208
|
+
top: 2px;
|
|
209
|
+
left: 2px;
|
|
210
|
+
width: 16px;
|
|
211
|
+
height: 16px;
|
|
212
|
+
border-radius: 50%;
|
|
213
|
+
background: var(--dsw-alias-label-secondary, #888);
|
|
214
|
+
transition: transform .2s ease, background .2s ease;
|
|
215
|
+
}
|
|
216
|
+
.dsh-hide-ui-config-row input:checked {
|
|
217
|
+
background: var(--dsw-alias-brand-primary, #4d6bfe);
|
|
218
|
+
border-color: var(--dsw-alias-brand-primary, #4d6bfe);
|
|
219
|
+
}
|
|
220
|
+
.dsh-hide-ui-config-row input:checked::before {
|
|
221
|
+
transform: translateX(16px);
|
|
222
|
+
background: #fff;
|
|
223
|
+
}
|
|
224
|
+
.dsh-hide-ui-config-row input:disabled {
|
|
225
|
+
opacity: .45;
|
|
226
|
+
cursor: default;
|
|
227
|
+
}
|
|
228
|
+
.dsh-hide-ui-config-row > span {
|
|
229
|
+
display: flex;
|
|
230
|
+
flex: 1;
|
|
231
|
+
flex-direction: column;
|
|
232
|
+
gap: 2px;
|
|
233
|
+
}
|
|
234
|
+
.dsh-hide-ui-config-hint {
|
|
235
|
+
display: block;
|
|
236
|
+
font-size: 11px;
|
|
237
|
+
line-height: 1.4;
|
|
238
|
+
opacity: .65;
|
|
239
|
+
}
|
|
240
|
+
.dsh-hide-ui-config-actions {
|
|
241
|
+
display: flex;
|
|
242
|
+
justify-content: flex-end;
|
|
243
|
+
gap: 8px;
|
|
244
|
+
margin-top: 10px;
|
|
245
|
+
}
|
|
246
|
+
.dsh-hide-ui-config-apply {
|
|
247
|
+
font: inherit;
|
|
248
|
+
font-size: 12px;
|
|
249
|
+
padding: 4px 10px;
|
|
250
|
+
border-radius: 6px;
|
|
251
|
+
border: 1px solid var(--dsw-alias-brand-primary, #4d6bfe);
|
|
252
|
+
background: var(--dsw-alias-brand-primary, #4d6bfe);
|
|
253
|
+
color: #fff;
|
|
254
|
+
cursor: pointer;
|
|
255
|
+
}
|
|
256
|
+
.dsh-hide-ui-config-close {
|
|
257
|
+
font: inherit;
|
|
258
|
+
font-size: 12px;
|
|
259
|
+
padding: 4px 10px;
|
|
260
|
+
border-radius: 6px;
|
|
261
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127, 127, 127, .35));
|
|
262
|
+
background: transparent;
|
|
263
|
+
color: inherit;
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
}
|
|
266
|
+
html[${ACTIVE_ATTR}="on"] .${TARGET_CLASS} {
|
|
267
|
+
visibility: hidden !important;
|
|
268
|
+
opacity: 0 !important;
|
|
269
|
+
pointer-events: none !important;
|
|
270
|
+
}
|
|
271
|
+
html[${ACTIVE_ATTR}="on"] .${FRAME_CLASS} {
|
|
272
|
+
background: transparent !important;
|
|
273
|
+
}
|
|
274
|
+
`;
|
|
275
|
+
|
|
276
|
+
function normalize(raw) {
|
|
277
|
+
const source = raw !== null && typeof raw === 'object' ? raw : {};
|
|
278
|
+
const regions = Array.isArray(source.regions)
|
|
279
|
+
? [...new Set(source.regions.filter((id) => REGION_IDS.includes(id)))]
|
|
280
|
+
: [...DEFAULT_SELECTION.regions];
|
|
281
|
+
return { shell: source.shell === true, regions };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function loadSelection() {
|
|
285
|
+
try {
|
|
286
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
287
|
+
if (raw === null) return normalize(DEFAULT_SELECTION);
|
|
288
|
+
return normalize(JSON.parse(raw));
|
|
289
|
+
} catch {
|
|
290
|
+
return normalize(DEFAULT_SELECTION);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function storeSelection(selection) {
|
|
295
|
+
try {
|
|
296
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(selection));
|
|
297
|
+
} catch {
|
|
298
|
+
// localStorage may be unavailable in private contexts.
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function apply(ctx) {
|
|
303
|
+
// A reloaded factory must not leave a previous copy of the stylesheet behind.
|
|
304
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
305
|
+
|
|
306
|
+
const style = document.createElement('style');
|
|
307
|
+
style.id = STYLE_ID;
|
|
308
|
+
style.dataset.plugin = 'dsh-hide-ui';
|
|
309
|
+
style.textContent = CSS;
|
|
310
|
+
(document.head ?? document.documentElement).appendChild(style);
|
|
311
|
+
|
|
312
|
+
let selection = loadSelection();
|
|
313
|
+
let active = false;
|
|
314
|
+
let focusBefore = null;
|
|
315
|
+
/** Swallow the rest of the click that restored the selected regions. */
|
|
316
|
+
let swallowLeft = false;
|
|
317
|
+
let swallowLeftTimer = 0;
|
|
318
|
+
/** Lightweight configuration popover opened with Ctrl+right-click. */
|
|
319
|
+
let configPanel = null;
|
|
320
|
+
|
|
321
|
+
function fallbackT(key) {
|
|
322
|
+
return en[key] ?? key;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function t(key) {
|
|
326
|
+
try {
|
|
327
|
+
if (ctx.locale !== undefined && typeof ctx.locale.bind === 'function') {
|
|
328
|
+
return ctx.locale.bind(NS)(key);
|
|
329
|
+
}
|
|
330
|
+
} catch {
|
|
331
|
+
// Fall through to the bundled English copy.
|
|
332
|
+
}
|
|
333
|
+
return fallbackT(key);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function clearSwallowLeft() {
|
|
337
|
+
swallowLeft = false;
|
|
338
|
+
if (swallowLeftTimer !== 0) {
|
|
339
|
+
window.clearTimeout(swallowLeftTimer);
|
|
340
|
+
swallowLeftTimer = 0;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function beginSwallowLeft() {
|
|
345
|
+
swallowLeft = true;
|
|
346
|
+
if (swallowLeftTimer !== 0) window.clearTimeout(swallowLeftTimer);
|
|
347
|
+
swallowLeftTimer = window.setTimeout(clearSwallowLeft, 700);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Resolve the layout columns from the shell structure, the way
|
|
352
|
+
* dsh-any-background does: the frame's children are [sidebar, center,
|
|
353
|
+
* rightbar?, overlay], so the columns are read by position around the
|
|
354
|
+
* stable `[data-shell-overlay]` and `data-rightbar-col` anchors instead of
|
|
355
|
+
* CSS-module suffixes that a host build can rename.
|
|
356
|
+
*/
|
|
357
|
+
function discoverParts() {
|
|
358
|
+
const overlay = document.querySelector('[data-shell-overlay]');
|
|
359
|
+
const frame =
|
|
360
|
+
overlay?.parentElement ??
|
|
361
|
+
document.querySelector('[class*="_frame"]') ??
|
|
362
|
+
document.getElementById('root');
|
|
363
|
+
if (frame === null) return { frame: null, center: null, sidebar: null, rightbar: null };
|
|
364
|
+
|
|
365
|
+
const children = frame.children;
|
|
366
|
+
let overlayIndex = -1;
|
|
367
|
+
let rightbar = null;
|
|
368
|
+
let rightIndex = -1;
|
|
369
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
370
|
+
const child = children[index];
|
|
371
|
+
if (child === overlay) overlayIndex = index;
|
|
372
|
+
else if (child instanceof HTMLElement && child.dataset.rightbarCol !== undefined) {
|
|
373
|
+
rightbar = child;
|
|
374
|
+
rightIndex = index;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let sidebar = null;
|
|
379
|
+
let center = null;
|
|
380
|
+
if (rightIndex >= 0) {
|
|
381
|
+
sidebar = children[rightIndex - 2] ?? null;
|
|
382
|
+
center = children[rightIndex - 1] ?? null;
|
|
383
|
+
} else if (overlayIndex >= 0) {
|
|
384
|
+
sidebar = children[overlayIndex - 3] ?? null;
|
|
385
|
+
center = children[overlayIndex - 2] ?? null;
|
|
386
|
+
}
|
|
387
|
+
return { frame, center, sidebar, rightbar };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function findFrame() {
|
|
391
|
+
return discoverParts().frame ?? document.getElementById('root');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function resolveTargetElements(id) {
|
|
395
|
+
const parts = discoverParts();
|
|
396
|
+
|
|
397
|
+
if (id === 'sidebar') {
|
|
398
|
+
const element = document.querySelector('[class*="_sidebarCol"]') ?? parts.sidebar;
|
|
399
|
+
return element === null ? [] : [element];
|
|
400
|
+
}
|
|
401
|
+
if (id === 'rightbar') {
|
|
402
|
+
const element =
|
|
403
|
+
document.querySelector('[data-rightbar-col]') ??
|
|
404
|
+
document.querySelector('[data-sidebar-right-panel]') ??
|
|
405
|
+
parts.rightbar;
|
|
406
|
+
return element === null ? [] : [element];
|
|
407
|
+
}
|
|
408
|
+
if (id === 'shell') {
|
|
409
|
+
const element = parts.frame ?? document.getElementById('root');
|
|
410
|
+
return element === null ? [] : [element];
|
|
411
|
+
}
|
|
412
|
+
if (id === 'chat') {
|
|
413
|
+
// Only the text flow, never the centre column: the column also owns
|
|
414
|
+
// the header bar and composer, which have their own toggles.
|
|
415
|
+
const flow = document.querySelector('[data-chat-flow]');
|
|
416
|
+
if (flow !== null) return [flow];
|
|
417
|
+
const content = document.querySelector('[data-conversation-content]');
|
|
418
|
+
if (content !== null) return [content];
|
|
419
|
+
return [];
|
|
420
|
+
}
|
|
421
|
+
if (id === 'headerBar') {
|
|
422
|
+
const anchor = document.querySelector('[data-slot^="conversation.session.header"]');
|
|
423
|
+
const bar =
|
|
424
|
+
anchor?.closest('header') ??
|
|
425
|
+
anchor?.querySelector('header') ??
|
|
426
|
+
anchor ??
|
|
427
|
+
document.querySelector('[class*="_headerBar"]');
|
|
428
|
+
return bar === null ? [] : [bar];
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const seen = new Set();
|
|
432
|
+
const elements = [];
|
|
433
|
+
for (const selector of TARGET_SELECTORS[id] ?? []) {
|
|
434
|
+
for (const element of document.querySelectorAll(selector)) {
|
|
435
|
+
if (!seen.has(element)) {
|
|
436
|
+
seen.add(element);
|
|
437
|
+
elements.push(element);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return elements;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function selectedElements() {
|
|
445
|
+
if (selection.shell) {
|
|
446
|
+
const elements = resolveTargetElements('shell');
|
|
447
|
+
// The desktop window menu lives outside the shell frame, so "entire
|
|
448
|
+
// window content" has to claim it explicitly.
|
|
449
|
+
const menu = document.querySelector('[data-windows-menu]');
|
|
450
|
+
if (menu !== null && !elements.includes(menu)) elements.push(menu);
|
|
451
|
+
return elements;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const seen = new Set();
|
|
455
|
+
const elements = [];
|
|
456
|
+
for (const id of selection.regions) {
|
|
457
|
+
for (const element of resolveTargetElements(id)) {
|
|
458
|
+
if (!seen.has(element)) {
|
|
459
|
+
seen.add(element);
|
|
460
|
+
elements.push(element);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return elements;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function markTargets() {
|
|
468
|
+
for (const element of selectedElements()) element.classList.add(TARGET_CLASS);
|
|
469
|
+
if (!selection.shell) {
|
|
470
|
+
const frame = findFrame();
|
|
471
|
+
if (frame !== null) frame.classList.add(FRAME_CLASS);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function unmarkTargets() {
|
|
476
|
+
for (const element of document.querySelectorAll(`.${TARGET_CLASS}`)) {
|
|
477
|
+
element.classList.remove(TARGET_CLASS);
|
|
478
|
+
}
|
|
479
|
+
for (const element of document.querySelectorAll(`.${FRAME_CLASS}`)) {
|
|
480
|
+
element.classList.remove(FRAME_CLASS);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function setActive(next) {
|
|
485
|
+
if (next === active) return;
|
|
486
|
+
|
|
487
|
+
if (next) {
|
|
488
|
+
const elements = selectedElements();
|
|
489
|
+
if (elements.length === 0) return;
|
|
490
|
+
active = true;
|
|
491
|
+
markTargets();
|
|
492
|
+
|
|
493
|
+
// A hidden composer must not keep receiving keystrokes.
|
|
494
|
+
const focused = document.activeElement;
|
|
495
|
+
if (focused !== null && elements.some((element) => element.contains(focused))) {
|
|
496
|
+
if (typeof focused.blur === 'function') {
|
|
497
|
+
focusBefore = focused;
|
|
498
|
+
focused.blur();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
document.documentElement.setAttribute(ACTIVE_ATTR, 'on');
|
|
502
|
+
} else {
|
|
503
|
+
active = false;
|
|
504
|
+
document.documentElement.removeAttribute(ACTIVE_ATTR);
|
|
505
|
+
unmarkTargets();
|
|
506
|
+
|
|
507
|
+
const focused = focusBefore;
|
|
508
|
+
focusBefore = null;
|
|
509
|
+
if (focused !== null && focused.isConnected && typeof focused.focus === 'function') {
|
|
510
|
+
try {
|
|
511
|
+
focused.focus({ preventScroll: true });
|
|
512
|
+
} catch {
|
|
513
|
+
// The element may have unmounted between activation and restore.
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function updateSelection(next) {
|
|
520
|
+
selection = normalize(next);
|
|
521
|
+
storeSelection(selection);
|
|
522
|
+
syncConfigInputs();
|
|
523
|
+
|
|
524
|
+
if (!active) return;
|
|
525
|
+
unmarkTargets();
|
|
526
|
+
if (selectedElements().length === 0) {
|
|
527
|
+
setActive(false);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
// Keep the active attribute and re-mark the new target set.
|
|
531
|
+
markTargets();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function syncConfigInputs() {
|
|
535
|
+
if (configPanel === null) return;
|
|
536
|
+
const shellInput = configPanel.querySelector('[data-region="shell"]');
|
|
537
|
+
const shell = shellInput !== null && shellInput.checked;
|
|
538
|
+
for (const input of configPanel.querySelectorAll('[data-region]')) {
|
|
539
|
+
if (input.dataset.region === 'shell') {
|
|
540
|
+
input.checked = selection.shell;
|
|
541
|
+
} else {
|
|
542
|
+
input.checked = selection.regions.includes(input.dataset.region);
|
|
543
|
+
input.disabled = shell;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function closeConfig() {
|
|
549
|
+
if (configPanel === null) return;
|
|
550
|
+
configPanel.remove();
|
|
551
|
+
configPanel = null;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function openConfig() {
|
|
555
|
+
closeConfig();
|
|
556
|
+
|
|
557
|
+
const panel = document.createElement('div');
|
|
558
|
+
panel.className = 'dsh-hide-ui-config';
|
|
559
|
+
panel.setAttribute('role', 'dialog');
|
|
560
|
+
panel.setAttribute('aria-label', t('nav'));
|
|
561
|
+
|
|
562
|
+
const title = document.createElement('div');
|
|
563
|
+
title.className = 'dsh-hide-ui-config-title';
|
|
564
|
+
title.textContent = t('nav');
|
|
565
|
+
panel.appendChild(title);
|
|
566
|
+
|
|
567
|
+
const inputs = new Map();
|
|
568
|
+
const addRow = (id, labelText, hintText) => {
|
|
569
|
+
const row = document.createElement('label');
|
|
570
|
+
row.className = 'dsh-hide-ui-config-row';
|
|
571
|
+
|
|
572
|
+
const input = document.createElement('input');
|
|
573
|
+
input.type = 'checkbox';
|
|
574
|
+
input.dataset.region = id;
|
|
575
|
+
input.addEventListener('change', () => {
|
|
576
|
+
const regions = REGION_IDS.filter((regionId) => inputs.get(regionId).checked);
|
|
577
|
+
updateSelection({ shell: inputs.get('shell').checked, regions });
|
|
578
|
+
});
|
|
579
|
+
inputs.set(id, input);
|
|
580
|
+
|
|
581
|
+
const text = document.createElement('span');
|
|
582
|
+
const label = document.createElement('span');
|
|
583
|
+
label.textContent = labelText;
|
|
584
|
+
const hint = document.createElement('span');
|
|
585
|
+
hint.className = 'dsh-hide-ui-config-hint';
|
|
586
|
+
hint.textContent = hintText;
|
|
587
|
+
text.appendChild(label);
|
|
588
|
+
text.appendChild(hint);
|
|
589
|
+
|
|
590
|
+
row.appendChild(input);
|
|
591
|
+
row.appendChild(text);
|
|
592
|
+
panel.appendChild(row);
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
for (const id of REGION_IDS) addRow(id, t(id), t(`${id}Hint`));
|
|
596
|
+
addRow('shell', t('shell'), t('shellHint'));
|
|
597
|
+
|
|
598
|
+
const actions = document.createElement('div');
|
|
599
|
+
actions.className = 'dsh-hide-ui-config-actions';
|
|
600
|
+
|
|
601
|
+
const apply = document.createElement('button');
|
|
602
|
+
apply.type = 'button';
|
|
603
|
+
apply.className = 'dsh-hide-ui-config-apply';
|
|
604
|
+
apply.textContent = t('hideNow');
|
|
605
|
+
apply.addEventListener('click', () => {
|
|
606
|
+
if (selectedElements().length === 0) return;
|
|
607
|
+
setActive(true);
|
|
608
|
+
closeConfig();
|
|
609
|
+
});
|
|
610
|
+
actions.appendChild(apply);
|
|
611
|
+
|
|
612
|
+
if (active) {
|
|
613
|
+
const restore = document.createElement('button');
|
|
614
|
+
restore.type = 'button';
|
|
615
|
+
restore.className = 'dsh-hide-ui-config-close';
|
|
616
|
+
restore.textContent = t('restore');
|
|
617
|
+
restore.addEventListener('click', () => {
|
|
618
|
+
setActive(false);
|
|
619
|
+
closeConfig();
|
|
620
|
+
});
|
|
621
|
+
actions.appendChild(restore);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const close = document.createElement('button');
|
|
625
|
+
close.type = 'button';
|
|
626
|
+
close.className = 'dsh-hide-ui-config-close';
|
|
627
|
+
close.textContent = t('close');
|
|
628
|
+
close.addEventListener('click', closeConfig);
|
|
629
|
+
actions.appendChild(close);
|
|
630
|
+
panel.appendChild(actions);
|
|
631
|
+
|
|
632
|
+
document.body.appendChild(panel);
|
|
633
|
+
configPanel = panel;
|
|
634
|
+
syncConfigInputs();
|
|
635
|
+
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function consume(event) {
|
|
639
|
+
event.preventDefault();
|
|
640
|
+
event.stopImmediatePropagation();
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** Whether a right-click belongs to the hide gesture. */
|
|
644
|
+
function ownsRightClick(target) {
|
|
645
|
+
if (active) return true;
|
|
646
|
+
// The conversation column is the gesture home: a sidebar-only or
|
|
647
|
+
// header-bar-only selection still hides when the user right-clicks the
|
|
648
|
+
// main area, instead of requiring a click on the exact target.
|
|
649
|
+
const parts = discoverParts();
|
|
650
|
+
if (parts.center !== null && parts.center.contains(target)) return true;
|
|
651
|
+
return selectedElements().some((element) => element.contains(target));
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Right-click inside a selected region toggles the hidden state; once
|
|
656
|
+
* hidden, a right-click anywhere restores. Left-clicking or Escape while
|
|
657
|
+
* hidden also restores, and the restoring click is swallowed. Alt+right-
|
|
658
|
+
* click opens the target picker without hiding anything.
|
|
659
|
+
*/
|
|
660
|
+
function onPointerDown(event) {
|
|
661
|
+
if (configPanel !== null) {
|
|
662
|
+
if (configPanel.contains(event.target)) return;
|
|
663
|
+
closeConfig();
|
|
664
|
+
if (event.button === 0 || event.button === 2) consume(event);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (event.button === 2) {
|
|
669
|
+
if (event.ctrlKey && !event.shiftKey) {
|
|
670
|
+
consume(event);
|
|
671
|
+
openConfig();
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (event.shiftKey || !ownsRightClick(event.target)) return;
|
|
675
|
+
consume(event);
|
|
676
|
+
setActive(!active);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (active && event.button === 0) {
|
|
681
|
+
// If a host re-render removed the markers, do not swallow the click.
|
|
682
|
+
if (document.querySelector(`.${TARGET_CLASS}`) === null) {
|
|
683
|
+
setActive(false);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
consume(event);
|
|
687
|
+
setActive(false);
|
|
688
|
+
beginSwallowLeft();
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function onContextMenu(event) {
|
|
693
|
+
if (configPanel !== null || event.ctrlKey) {
|
|
694
|
+
consume(event);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (event.shiftKey || !ownsRightClick(event.target)) return;
|
|
698
|
+
consume(event);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Own the rest of the gesture as well: compatibility mouse events fire
|
|
703
|
+
* after pointerdown, and the restore click must not reach a control that
|
|
704
|
+
* just became visible again.
|
|
705
|
+
*/
|
|
706
|
+
function onMouseEvent(event) {
|
|
707
|
+
if (configPanel !== null) {
|
|
708
|
+
if (configPanel.contains(event.target)) return;
|
|
709
|
+
if (event.button === 0 || event.button === 2) consume(event);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (event.button === 2) {
|
|
714
|
+
if (event.ctrlKey) {
|
|
715
|
+
consume(event);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (event.shiftKey || !ownsRightClick(event.target)) return;
|
|
719
|
+
consume(event);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (swallowLeft && event.button === 0) {
|
|
724
|
+
consume(event);
|
|
725
|
+
if (event.type === 'click') clearSwallowLeft();
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function onKeyDown(event) {
|
|
730
|
+
if (configPanel !== null) {
|
|
731
|
+
if (event.key === 'Escape' || event.key === 'Esc') {
|
|
732
|
+
consume(event);
|
|
733
|
+
closeConfig();
|
|
734
|
+
}
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (!active) return;
|
|
738
|
+
if (event.key === 'Escape' || event.key === 'Esc') {
|
|
739
|
+
consume(event);
|
|
740
|
+
setActive(false);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function SettingsSection(props) {
|
|
745
|
+
const [value, setValue] = React.useState(loadSelection);
|
|
746
|
+
const translate = typeof props.t === 'function' ? props.t : t;
|
|
747
|
+
|
|
748
|
+
const update = (next) => {
|
|
749
|
+
const normalized = normalize(next);
|
|
750
|
+
setValue(normalized);
|
|
751
|
+
updateSelection(normalized);
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
const toggleRegion = (id) => {
|
|
755
|
+
const regions = value.regions.includes(id)
|
|
756
|
+
? value.regions.filter((region) => region !== id)
|
|
757
|
+
: [...value.regions, id];
|
|
758
|
+
update({ ...value, regions });
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
const row = (id, label, hint, disabled) => React.createElement(
|
|
762
|
+
'label',
|
|
763
|
+
{
|
|
764
|
+
key: id,
|
|
765
|
+
className: 'dsh-hide-ui-config-row',
|
|
766
|
+
style: {
|
|
767
|
+
opacity: disabled ? 0.5 : 1,
|
|
768
|
+
cursor: disabled ? 'default' : 'pointer',
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
[
|
|
772
|
+
React.createElement('input', {
|
|
773
|
+
key: 'input',
|
|
774
|
+
type: 'checkbox',
|
|
775
|
+
checked: value.regions.includes(id),
|
|
776
|
+
disabled,
|
|
777
|
+
onChange: () => toggleRegion(id),
|
|
778
|
+
style: { marginTop: 3, accentColor: 'var(--dsw-alias-brand-primary, #4d6bfe)' },
|
|
779
|
+
}),
|
|
780
|
+
React.createElement(
|
|
781
|
+
'span',
|
|
782
|
+
{ key: 'text', style: { display: 'flex', flexDirection: 'column', gap: 2 } },
|
|
783
|
+
[
|
|
784
|
+
React.createElement('span', { key: 'label', style: { fontWeight: 500 } }, label),
|
|
785
|
+
React.createElement(
|
|
786
|
+
'span',
|
|
787
|
+
{ key: 'hint', style: { fontSize: 12, lineHeight: 1.45, opacity: 0.65 } },
|
|
788
|
+
hint,
|
|
789
|
+
),
|
|
790
|
+
],
|
|
791
|
+
),
|
|
792
|
+
],
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
return React.createElement(
|
|
796
|
+
'div',
|
|
797
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 640, padding: '4px 0' } },
|
|
798
|
+
[
|
|
799
|
+
React.createElement(
|
|
800
|
+
'p',
|
|
801
|
+
{ key: 'description', style: { margin: 0, fontSize: 13, lineHeight: 1.55, opacity: 0.8 } },
|
|
802
|
+
translate('desc'),
|
|
803
|
+
),
|
|
804
|
+
React.createElement('div', { key: 'regions', style: { display: 'flex', flexDirection: 'column', gap: 10 } }, [
|
|
805
|
+
...REGION_IDS.map((id) => row(id, translate(id), translate(`${id}Hint`), value.shell)),
|
|
806
|
+
]),
|
|
807
|
+
row('shell', translate('shell'), translate('shellHint'), false),
|
|
808
|
+
!value.shell && value.regions.length === 0
|
|
809
|
+
? React.createElement(
|
|
810
|
+
'p',
|
|
811
|
+
{
|
|
812
|
+
key: 'empty',
|
|
813
|
+
style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-state-error-primary, #d33)' },
|
|
814
|
+
},
|
|
815
|
+
translate('empty'),
|
|
816
|
+
)
|
|
817
|
+
: null,
|
|
818
|
+
React.createElement('div', { key: 'actions', style: { display: 'flex', gap: 8 } }, [
|
|
819
|
+
React.createElement(
|
|
820
|
+
'button',
|
|
821
|
+
{
|
|
822
|
+
key: 'reset',
|
|
823
|
+
type: 'button',
|
|
824
|
+
onClick: () => update({ shell: false, regions: ['chat'] }),
|
|
825
|
+
style: {
|
|
826
|
+
font: 'inherit',
|
|
827
|
+
fontSize: 12,
|
|
828
|
+
padding: '5px 10px',
|
|
829
|
+
borderRadius: 6,
|
|
830
|
+
border: '1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.35))',
|
|
831
|
+
background: 'transparent',
|
|
832
|
+
color: 'inherit',
|
|
833
|
+
cursor: 'pointer',
|
|
834
|
+
},
|
|
835
|
+
},
|
|
836
|
+
translate('reset'),
|
|
837
|
+
),
|
|
838
|
+
]),
|
|
839
|
+
],
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function registerSettings() {
|
|
844
|
+
if (React === null) {
|
|
845
|
+
console.warn('dsh-hide-ui: settings section disabled because React could not be resolved');
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
if (ctx.slots === undefined || ctx.locale === undefined) {
|
|
849
|
+
console.warn('dsh-hide-ui: settings section disabled because slots/locale are unavailable');
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
try {
|
|
854
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-hide-ui: i18n');
|
|
855
|
+
} catch (error) {
|
|
856
|
+
console.warn('dsh-hide-ui: locale registration failed', error);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
try {
|
|
860
|
+
// The settings slot expects a store spec with create(), not a bare
|
|
861
|
+
// snapshot: the host calls create() once and binds useStore/actions.
|
|
862
|
+
const emptyStore = {
|
|
863
|
+
create: () => ({
|
|
864
|
+
getSnapshot: () => ({ selection: loadSelection() }),
|
|
865
|
+
subscribe: () => () => {},
|
|
866
|
+
actions: {},
|
|
867
|
+
}),
|
|
868
|
+
};
|
|
869
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
870
|
+
name: 'settings.section',
|
|
871
|
+
id: 'dsh-hide-ui',
|
|
872
|
+
order: 70,
|
|
873
|
+
label: () => ctx.locale.bind(NS)('nav'),
|
|
874
|
+
locale: NS,
|
|
875
|
+
store: emptyStore,
|
|
876
|
+
inject: () => ({}),
|
|
877
|
+
}, SettingsSection));
|
|
878
|
+
} catch (error) {
|
|
879
|
+
console.warn('dsh-hide-ui: settings section registration failed', error);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
window.addEventListener('pointerdown', onPointerDown, true);
|
|
884
|
+
window.addEventListener('mousedown', onMouseEvent, true);
|
|
885
|
+
window.addEventListener('mouseup', onMouseEvent, true);
|
|
886
|
+
window.addEventListener('pointerup', onMouseEvent, true);
|
|
887
|
+
window.addEventListener('click', onMouseEvent, true);
|
|
888
|
+
window.addEventListener('auxclick', onMouseEvent, true);
|
|
889
|
+
window.addEventListener('contextmenu', onContextMenu, true);
|
|
890
|
+
window.addEventListener('keydown', onKeyDown, true);
|
|
891
|
+
|
|
892
|
+
registerSettings();
|
|
893
|
+
|
|
894
|
+
ctx.effect(() => () => {
|
|
895
|
+
window.removeEventListener('pointerdown', onPointerDown, true);
|
|
896
|
+
window.removeEventListener('mousedown', onMouseEvent, true);
|
|
897
|
+
window.removeEventListener('mouseup', onMouseEvent, true);
|
|
898
|
+
window.removeEventListener('pointerup', onMouseEvent, true);
|
|
899
|
+
window.removeEventListener('click', onMouseEvent, true);
|
|
900
|
+
window.removeEventListener('auxclick', onMouseEvent, true);
|
|
901
|
+
window.removeEventListener('contextmenu', onContextMenu, true);
|
|
902
|
+
window.removeEventListener('keydown', onKeyDown, true);
|
|
903
|
+
closeConfig();
|
|
904
|
+
clearSwallowLeft();
|
|
905
|
+
setActive(false);
|
|
906
|
+
style.remove();
|
|
907
|
+
}, 'dsh-hide-ui: hide UI regions');
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return { name: 'dsh-hide-ui', inject: ['slots', 'locale'], apply };
|
|
911
|
+
},
|
|
912
|
+
});
|
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Hide UI peek">
|
|
2
|
+
<rect width="64" height="64" rx="14" fill="#1f2430"/>
|
|
3
|
+
<path d="M10 32c6-9 13-13 22-13s16 4 22 13c-6 9-13 13-22 13S16 41 10 32z" fill="#e8ecf7"/>
|
|
4
|
+
<circle cx="32" cy="32" r="7" fill="#1f2430"/>
|
|
5
|
+
<path d="M14 50 50 14" stroke="#4d6bfe" stroke-width="6" stroke-linecap="round"/>
|
|
6
|
+
</svg>
|
package/index.js
ADDED
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-hide-ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.4",
|
|
4
|
+
"description": "Right-click to hide selected DeepSeek Harness surfaces (sidebar, cards, input, conversation, trajectory, produced content, header, right sidebar) and reveal the background wallpaper.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"deepseek-harness",
|
|
7
|
+
"dsh",
|
|
8
|
+
"dsh-plugin",
|
|
9
|
+
"wallpaper",
|
|
10
|
+
"peek"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./index.js",
|
|
16
|
+
"./client": "./client.js",
|
|
17
|
+
"./locale/*.json": "./locale/*.json",
|
|
18
|
+
"./icon": "./icon.svg"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"index.js",
|
|
22
|
+
"client.js",
|
|
23
|
+
"cordis.patch.yml",
|
|
24
|
+
"locale/*.json",
|
|
25
|
+
"icon.svg",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"dsh": {
|
|
30
|
+
"bundle": {
|
|
31
|
+
"patch": "./cordis.patch.yml"
|
|
32
|
+
},
|
|
33
|
+
"client": {
|
|
34
|
+
"platform": "web",
|
|
35
|
+
"immediately": true,
|
|
36
|
+
"inject": []
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|