dsh-focus-overlay 0.12.3 → 0.12.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/README.en.md +15 -9
- package/README.md +15 -9
- package/lib/client.js +313 -50
- package/package.json +3 -2
package/README.en.md
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
<img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version">
|
|
14
|
-
<img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)">
|
|
15
|
-
<img src="https://
|
|
16
|
-
<img src="https://
|
|
13
|
+
<a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version"></a>
|
|
14
|
+
<a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)"></a>
|
|
15
|
+
<a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license: MIT"></a>
|
|
16
|
+
<a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/package.json"><img src="https://img.shields.io/badge/dsh-%3E%3D0.1.2--rc.1-blue" alt="dsh version"></a>
|
|
17
|
+
<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://img.shields.io/badge/awesome-dsh--plugin-orange" alt="awesome dsh plugin"></a>
|
|
17
18
|
</p>
|
|
18
19
|
|
|
19
20
|
## Features
|
|
@@ -75,12 +76,13 @@ Click the line to expand that turn's full work detail (per-category one-line sum
|
|
|
75
76
|
| Focus counter | A row at the bottom of the settings card totals your focus-mode entries (auto-entries included), persisted to `localStorage`; past 100 entries a Star nudge appears — after the first use it is remembered and replaced by a direct "open the project page" button |
|
|
76
77
|
| Esc peels layer by layer | Answer card → input bar → focus mode, one layer per press; Esc mid-IME-composition cancels the composition first |
|
|
77
78
|
| i18n | Chinese / English copy registered under the `focus` namespace, follows the UI language |
|
|
78
|
-
| Plugin configuration
|
|
79
|
-
| First-run onboarding | After installing, the welcome page shows a one-time "Focus Mode" intro (feature overview + in-place configuration) as a `settings.onboarding` step with its own versioned seen-flag |
|
|
80
|
-
| Official preference sync | Reads the official "transcript view" preference (normal / compact); flipping it applies immediately |
|
|
79
|
+
| Plugin configuration | On dsh 0.2 and newer, open this plugin's detail page from the sidebar "Plugins" page — the configuration renders between the package description and its component rows (`plugins.bundle.config`). On dsh ≤ 0.1.x it stays the collapsible "Settings → Plugins → Plugin configuration" card (`settings.plugin.item`). Preferences persist to `localStorage`; the Node half registers the same settings namespace (schemastery schema) |
|
|
80
|
+
| First-run onboarding | After installing, the welcome page shows a one-time "Focus Mode" intro (feature overview + in-place configuration) as a `settings.onboarding` step with its own versioned seen-flag; its "open settings" action jumps to whichever configuration surface the running dsh declares |
|
|
81
|
+
| Official preference sync | Reads the official "transcript view" preference (normal / compact on dsh ≤ 0.1.x; compact / standard / detailed / verbose on dsh 0.2); flipping it applies immediately |
|
|
81
82
|
| Render error boundary | The overlay content is wrapped in an error boundary: one render crash never bricks the Focus button — the next open retries |
|
|
82
83
|
| DSH-better-sidebar compatibility | While in focus, a `<body>` marker makes the package stylesheet hide better-sidebar's top-right panel toggles and any open right/bottom panels (and release the squeezed layout); restored on exit |
|
|
83
84
|
| Legacy compatibility mode | On dsh < 0.1.2 missing the new services, the overlay degrades gracefully (legacy snapshot adapters) with a one-time console notice — no crash |
|
|
85
|
+
| Cross-release primitive compatibility | Official primitives renamed between major releases fall back by availability (dsh 0.2 removed the fixed-size `…14` / `…16` icons in favour of `…Regular` + `size`), so an `undefined` component can never throw React #130 |
|
|
84
86
|
|
|
85
87
|
## Install
|
|
86
88
|
|
|
@@ -119,7 +121,7 @@ dsh web
|
|
|
119
121
|
|
|
120
122
|
Restart `dsh web` after install.
|
|
121
123
|
|
|
122
|
-
> **First run**: after restarting, the welcome page shows a one-time "Focus Mode" intro that explains the features and lets you configure them in place; all of these settings can be changed anytime
|
|
124
|
+
> **First run**: after restarting, the welcome page shows a one-time "Focus Mode" intro that explains the features and lets you configure them in place; all of these settings can be changed anytime — on newer dsh open this plugin's detail page from the sidebar "Plugins" page, on older dsh use "Settings → Plugins → Plugin configuration".
|
|
123
125
|
|
|
124
126
|
## Usage
|
|
125
127
|
|
|
@@ -133,7 +135,11 @@ Restart `dsh web` after install.
|
|
|
133
135
|
|
|
134
136
|
## Settings
|
|
135
137
|
|
|
136
|
-
|
|
138
|
+
**On dsh 0.2 and newer**: sidebar "Plugins" page → find `dsh-focus-overlay` → the configuration shows on its detail page, between the package description and the component rows.
|
|
139
|
+
|
|
140
|
+
**On dsh ≤ 0.1.x**: sidebar "Settings → Plugins → Plugin configuration" → expand the "Focus Mode" card.
|
|
141
|
+
|
|
142
|
+
Both locations present the same options:
|
|
137
143
|
|
|
138
144
|
| Option | Description |
|
|
139
145
|
| --- | --- |
|
package/README.md
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
|
-
<img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version">
|
|
12
|
-
<img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)">
|
|
13
|
-
<img src="https://
|
|
14
|
-
<img src="https://
|
|
11
|
+
<a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/v/dsh-focus-overlay" alt="npm version"></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/dsh-focus-overlay"><img src="https://img.shields.io/npm/dm/dsh-focus-overlay" alt="npm downloads (monthly)"></a>
|
|
13
|
+
<a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license: MIT"></a>
|
|
14
|
+
<a href="https://github.com/boogoo619/dsh-focus-overlay/blob/master/package.json"><img src="https://img.shields.io/badge/dsh-%3E%3D0.1.2--rc.1-blue" alt="dsh version"></a>
|
|
15
|
+
<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://img.shields.io/badge/awesome-dsh--plugin-orange" alt="awesome dsh plugin"></a>
|
|
15
16
|
</p>
|
|
16
17
|
|
|
17
18
|
## 功能
|
|
@@ -73,12 +74,13 @@
|
|
|
73
74
|
| 专注计数 | 设置卡片最底部一行累计进入专注模式的次数(含自动进入),持久化到 `localStorage`;超过 100 次出现 Star 引导按钮,点击前往仓库页面并记住状态,之后改为「前往 GitHub 项目页面」直达按钮 |
|
|
74
75
|
| Esc 逐层退出 | 答题卡 → 输入条 → 专注模式,一层一层退;输入法组词中的 Esc 优先取消组词 |
|
|
75
76
|
| i18n | 中 / 英文案,注册到 `focus` 命名空间,跟随界面语言 |
|
|
76
|
-
| 插件配置卡片 |
|
|
77
|
-
| 首次引导 | 首次安装后欢迎页弹出一次「专注模式」引导(功能一览 + 就地配置),`settings.onboarding`
|
|
78
|
-
| 官方偏好联动 |
|
|
77
|
+
| 插件配置卡片 | dsh 0.2 起在侧栏「Plugins」页面打开本插件详情页,配置直接渲染在包描述与组件列表之间(`plugins.bundle.config`);dsh ≤ 0.1.x 仍为「设置 → 插件 → 插件配置」中的可折叠卡片(`settings.plugin.item`)。偏好持久化到 `localStorage`;Node 半区以 schemastery schema 注册同名设置 namespace |
|
|
78
|
+
| 首次引导 | 首次安装后欢迎页弹出一次「专注模式」引导(功能一览 + 就地配置),`settings.onboarding` 步骤自带已读标记与版本号;「打开设置」按当前 dsh 版本跳到对应配置页 |
|
|
79
|
+
| 官方偏好联动 | 读取官方「对话显示」偏好(dsh ≤ 0.1.x:normal / compact;dsh 0.2:compact / standard / detailed / verbose),切换即时生效 |
|
|
79
80
|
| 渲染错误兜底 | 遮罩内容包裹错误边界:一次渲染崩溃不会永久打死「专注」按钮,下次打开自动重试 |
|
|
80
81
|
| 兼容 DSH-better-sidebar | 进入专注时在 `<body>` 打标记,样式层自动隐藏其右上角的收放面板按钮与已开启的右侧 / 底部面板,并释放被挤压的布局;退出专注后按原状态还原 |
|
|
81
82
|
| 旧版兼容模式 | dsh < 0.1.2 缺少新服务时降级运行(legacy 快照适配),控制台一次性提示,不会崩溃 |
|
|
83
|
+
| 跨版本构件兼容 | 图标等官方构件在两个大版本间改名时按可用性回退(dsh 0.2 移除了 `…14` / `…16` 定尺图标,改用 `…Regular` + `size`),不会因为 `undefined` 组件触发 React #130 |
|
|
82
84
|
|
|
83
85
|
## 安装
|
|
84
86
|
|
|
@@ -117,7 +119,7 @@ dsh web
|
|
|
117
119
|
|
|
118
120
|
安装后重启 `dsh web` 生效。
|
|
119
121
|
|
|
120
|
-
>
|
|
122
|
+
> **首次安装**:重启后欢迎页会弹出一次「专注模式」引导,说明功能并让你就地完成配置;其中所有设置随时可以更改——新版 dsh 在侧栏「Plugins」页面中打开本插件的详情页,旧版在「设置 → 插件 → 插件配置」中。
|
|
121
123
|
|
|
122
124
|
## 使用
|
|
123
125
|
|
|
@@ -131,7 +133,11 @@ dsh web
|
|
|
131
133
|
|
|
132
134
|
## 设置
|
|
133
135
|
|
|
134
|
-
|
|
136
|
+
**dsh 0.2 起**:侧栏「Plugins」页面 → 找到 `dsh-focus-overlay` → 详情页中,配置直接显示在包描述与组件列表之间。
|
|
137
|
+
|
|
138
|
+
**dsh ≤ 0.1.x**:侧栏「设置 → 插件 → 插件配置」→ 展开「专注模式」卡片。
|
|
139
|
+
|
|
140
|
+
两种位置呈现同一组选项:
|
|
135
141
|
|
|
136
142
|
| 选项 | 说明 |
|
|
137
143
|
| --- | --- |
|
package/lib/client.js
CHANGED
|
@@ -3,9 +3,62 @@ window.__ModuleLoader__.load({
|
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
module.exports;
|
|
6
|
+
//#region \0rolldown/runtime.js
|
|
7
|
+
var __create = Object.create;
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
12
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
15
|
+
key = keys[i];
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
17
|
+
get: ((k) => from[k]).bind(null, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
//#endregion
|
|
6
28
|
let react = require("react");
|
|
7
29
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
30
|
+
_deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
|
|
8
31
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
32
|
+
//#region src/client/icons.ts
|
|
33
|
+
/** Cross-release product-icon resolution.
|
|
34
|
+
*
|
|
35
|
+
* The official primitives package renamed its icon set in dsh 0.2: the
|
|
36
|
+
* fixed-size artwork variants (`IconCheckOutline14`, `IconChevronDownOutline14`,
|
|
37
|
+
* `IconEditOutline16`, `IconSendOutline14`) were removed, leaving one
|
|
38
|
+
* `…OutlineRegular` / `…OutlineMedium` component per glyph that takes an
|
|
39
|
+
* explicit `size` prop. A stale named import therefore resolves to
|
|
40
|
+
* `undefined`, and React throws #130 ("element type is invalid") the moment
|
|
41
|
+
* the component renders — the same failure mode the 0.1.3 `MessageText`
|
|
42
|
+
* removal produced.
|
|
43
|
+
*
|
|
44
|
+
* Import the namespace once and pick the first name the running build exports,
|
|
45
|
+
* so one bundle serves both generations. Each fallback carries the size the
|
|
46
|
+
* removed variant was drawn at, keeping layout identical across releases. */
|
|
47
|
+
function pick(names, size) {
|
|
48
|
+
for (const name of names) {
|
|
49
|
+
const component = _deepseek_ai_dsh_client_ui_primitives[name];
|
|
50
|
+
if (typeof component === "function") return (props) => component({
|
|
51
|
+
size,
|
|
52
|
+
...props
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return () => null;
|
|
56
|
+
}
|
|
57
|
+
const IconCheck = pick(["IconCheckOutline14", "IconCheckOutlineRegular"], 14);
|
|
58
|
+
const IconChevronDown = pick(["IconChevronDownOutline14", "IconChevronDownOutlineRegular"], 14);
|
|
59
|
+
const IconEdit = pick(["IconEditOutline16", "IconEditOutlineRegular"], 16);
|
|
60
|
+
const IconSend = pick(["IconSendOutline14", "IconSendOutlineRegular"], 14);
|
|
61
|
+
//#endregion
|
|
9
62
|
//#region src/client/model.ts
|
|
10
63
|
function legacySliceOf(chatView, snap) {
|
|
11
64
|
const legacy = chatView && chatView.legacy ? chatView.legacy : null;
|
|
@@ -25,6 +78,61 @@ window.__ModuleLoader__.load({
|
|
|
25
78
|
} catch {}
|
|
26
79
|
return null;
|
|
27
80
|
}
|
|
81
|
+
/** dsh 0.2 moved the pending interaction off the uiSession
|
|
82
|
+
* `pendingInteractions` map and onto the unified `sessionStatus` snapshot
|
|
83
|
+
* (`ReadonlyMap<sessionId, { running, pendingInteraction, completionUnread }>`).
|
|
84
|
+
* Both carriers answer with the same interaction face, so the overlay's answer
|
|
85
|
+
* card resolves through either. Null when nothing is pending. */
|
|
86
|
+
function pendingInteractionFromStatus(status, sessionId) {
|
|
87
|
+
try {
|
|
88
|
+
const entry = status && typeof status.get === "function" ? status.get(sessionId) : null;
|
|
89
|
+
return entry && entry.pendingInteraction || null;
|
|
90
|
+
} catch {}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
/** The current session id, resolved across dsh generations.
|
|
94
|
+
*
|
|
95
|
+
* dsh <= 0.1.x carried the viewer's selection on the session-list snapshot
|
|
96
|
+
* (`sessions.list.getSnapshot().current`). dsh 0.2 dropped that field: the
|
|
97
|
+
* selection is the session the main view retains, published on the list rows
|
|
98
|
+
* themselves (`byId[id].retainedBy.mainView > 0`) — the same public read the
|
|
99
|
+
* official cordis inspector uses. `mainBinding` is the uiSession main-binding
|
|
100
|
+
* snapshot, kept as a guarded last resort: it carries the same id, but that
|
|
101
|
+
* member is typed private, so the public list data stays primary. */
|
|
102
|
+
function currentSessionIdOf(listState, mainBinding) {
|
|
103
|
+
const fromList = listState ? listState.current : void 0;
|
|
104
|
+
if (fromList !== void 0 && fromList !== null) return fromList;
|
|
105
|
+
const byId = listState && listState.byId;
|
|
106
|
+
if (byId && typeof byId === "object") {
|
|
107
|
+
for (const row of Object.values(byId)) if (row && (row.retainedBy && row.retainedBy.mainView || 0) > 0) return row.id;
|
|
108
|
+
}
|
|
109
|
+
if (mainBinding) {
|
|
110
|
+
const id = mainBinding.key ?? (mainBinding.props ? mainBinding.props.sessionId : void 0);
|
|
111
|
+
if (id !== void 0 && id !== null) return id;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** The overlay fold mode derived from the official "对话显示" preference.
|
|
115
|
+
*
|
|
116
|
+
* dsh <= 0.1.x served it through the `settingsScope` service bound to the
|
|
117
|
+
* `ui-chat` namespace (`{ transcriptView: 'normal' | 'compact' }`). dsh 0.2
|
|
118
|
+
* retired `settingsScope`; the same preference is a Host settings field read
|
|
119
|
+
* through `configForms.get('ui-chat')`, whose snapshot nests it under `value`
|
|
120
|
+
* (and widened the accepted set to compact | standard | detailed | verbose,
|
|
121
|
+
* with `normal` / `expanded` kept as legacy saved values). Only `compact`
|
|
122
|
+
* folds the tool flow; every other explicit mode reads as the expanded
|
|
123
|
+
* presentation. Absence keeps the overlay's own compact default. */
|
|
124
|
+
function transcriptViewOf(scopeOrForm) {
|
|
125
|
+
try {
|
|
126
|
+
if (!scopeOrForm || typeof scopeOrForm.getSnapshot !== "function") return "compact";
|
|
127
|
+
const snapshot = scopeOrForm.getSnapshot();
|
|
128
|
+
const section = snapshot && snapshot.value && typeof snapshot.value === "object" ? snapshot.value : snapshot;
|
|
129
|
+
const mode = section ? section.transcriptView : null;
|
|
130
|
+
if (mode === void 0 || mode === null) return "compact";
|
|
131
|
+
return mode === "compact" ? "compact" : "normal";
|
|
132
|
+
} catch {
|
|
133
|
+
return "compact";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
28
136
|
/** Legacy pending interaction — pre-0.1.2 dsh carried a flat `pending` array
|
|
29
137
|
* on the session snapshot, with a different carrier shape (`payload`
|
|
30
138
|
* envelope, answers via `wait.respond({ok, value})` returning a `receipt`).
|
|
@@ -70,6 +178,28 @@ window.__ModuleLoader__.load({
|
|
|
70
178
|
});
|
|
71
179
|
return shaped;
|
|
72
180
|
}
|
|
181
|
+
/** Build the `dsh-resource://file/session/<id>/<path>` address that dsh 0.2's
|
|
182
|
+
* Sidebar resource controller opens a file through.
|
|
183
|
+
*
|
|
184
|
+
* The official `fileAddressFor` lives in `@deepseek-ai/dsh-util-workspace-path`,
|
|
185
|
+
* but the client bundle purity gate keeps feature bundles from importing other
|
|
186
|
+
* plugins' values across the boundary — the official Chat package inlines its
|
|
187
|
+
* own copy of the same grammar, and so does this one. Absolute paths inside the
|
|
188
|
+
* session workspace are reduced to workspace-relative; every id and path
|
|
189
|
+
* segment is component-encoded, with `:` kept literal for Windows drives. */
|
|
190
|
+
function sessionFileAddressOf(sessionId, cwd, path) {
|
|
191
|
+
const encode = (segment) => encodeURIComponent(segment).replace(/%3A/gi, ":");
|
|
192
|
+
const normalized = String(path).replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
|
|
193
|
+
const absolute = normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized);
|
|
194
|
+
let relative = normalized;
|
|
195
|
+
if (absolute) {
|
|
196
|
+
const root = cwd === void 0 || cwd === null ? "" : String(cwd).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
197
|
+
if (root !== "" && normalized === root) relative = "";
|
|
198
|
+
else if (root !== "" && normalized.startsWith(root + "/")) relative = normalized.slice(root.length + 1);
|
|
199
|
+
}
|
|
200
|
+
const body = relative.split("/").map(encode).join("/");
|
|
201
|
+
return `dsh-resource://file/session/${encode(String(sessionId))}/${body}`;
|
|
202
|
+
}
|
|
73
203
|
function flattenText(content) {
|
|
74
204
|
if (!content || !content.length) return "";
|
|
75
205
|
let out = "";
|
|
@@ -933,7 +1063,7 @@ window.__ModuleLoader__.load({
|
|
|
933
1063
|
"onboarding.feature.autoFocus": "自动专注与提醒 —— 回复完成自动进入专注,AI 提问 / 等待时提醒",
|
|
934
1064
|
"onboarding.feature.hotkey": "快捷键 F —— 任意界面按 F 一键进入专注,可在设置中关闭",
|
|
935
1065
|
"onboarding.configure.title": "快速配置",
|
|
936
|
-
"onboarding.laterNote": "
|
|
1066
|
+
"onboarding.laterNote": "这些设置随时可以更改:新版 dsh 在侧栏「Plugins」页面中打开本插件的详情页,旧版在「设置 → 插件 → 插件配置」中。",
|
|
937
1067
|
"onboarding.openSettings": "打开设置",
|
|
938
1068
|
"onboarding.done": "完成"
|
|
939
1069
|
};
|
|
@@ -1019,7 +1149,7 @@ window.__ModuleLoader__.load({
|
|
|
1019
1149
|
"onboarding.feature.autoFocus": "Auto-focus & reminders — auto-enter after a reply, notify while the AI waits",
|
|
1020
1150
|
"onboarding.feature.hotkey": "Hotkey F — press F anywhere to enter focus mode (toggleable in settings)",
|
|
1021
1151
|
"onboarding.configure.title": "Quick setup",
|
|
1022
|
-
"onboarding.laterNote": "
|
|
1152
|
+
"onboarding.laterNote": "Change these anytime: on newer dsh open this plugin’s detail page from the sidebar Plugins page; on older dsh use Settings → Plugins → Plugin configuration.",
|
|
1023
1153
|
"onboarding.openSettings": "Open settings",
|
|
1024
1154
|
"onboarding.done": "Done"
|
|
1025
1155
|
};
|
|
@@ -1109,7 +1239,7 @@ window.__ModuleLoader__.load({
|
|
|
1109
1239
|
"aria-pressed": on,
|
|
1110
1240
|
children: [multi ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1111
1241
|
className: "fm-opt-check" + (on ? " fm-opt-check-on" : ""),
|
|
1112
|
-
children: on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1242
|
+
children: on ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheck, {}) : null
|
|
1113
1243
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1114
1244
|
className: "fm-opt-num",
|
|
1115
1245
|
children: index + 1
|
|
@@ -1220,7 +1350,7 @@ window.__ModuleLoader__.load({
|
|
|
1220
1350
|
title: t("answer.close"),
|
|
1221
1351
|
"aria-label": t("answer.close"),
|
|
1222
1352
|
onClick: onClose,
|
|
1223
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1353
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, {})
|
|
1224
1354
|
})]
|
|
1225
1355
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1226
1356
|
className: "fm-card-head",
|
|
@@ -1236,7 +1366,7 @@ window.__ModuleLoader__.load({
|
|
|
1236
1366
|
title: t("answer.close"),
|
|
1237
1367
|
"aria-label": t("answer.close"),
|
|
1238
1368
|
onClick: onClose,
|
|
1239
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1369
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, {})
|
|
1240
1370
|
})
|
|
1241
1371
|
]
|
|
1242
1372
|
}),
|
|
@@ -1434,7 +1564,7 @@ window.__ModuleLoader__.load({
|
|
|
1434
1564
|
disabled: empty,
|
|
1435
1565
|
onMouseDown: (e) => e.preventDefault(),
|
|
1436
1566
|
onClick: onSend,
|
|
1437
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1567
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSend, {})
|
|
1438
1568
|
})
|
|
1439
1569
|
]
|
|
1440
1570
|
})
|
|
@@ -1471,7 +1601,7 @@ window.__ModuleLoader__.load({
|
|
|
1471
1601
|
title: t("composer.expand"),
|
|
1472
1602
|
"aria-label": t("composer.expand"),
|
|
1473
1603
|
onClick: onExpand,
|
|
1474
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1604
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEdit, {}), hasDraft ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "fm-pill-dot" }) : null]
|
|
1475
1605
|
});
|
|
1476
1606
|
}
|
|
1477
1607
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -1480,7 +1610,7 @@ window.__ModuleLoader__.load({
|
|
|
1480
1610
|
title: t("backToLatest"),
|
|
1481
1611
|
"aria-label": t("backToLatest"),
|
|
1482
1612
|
onClick: onJumpBottom,
|
|
1483
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1613
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { size: 16 })
|
|
1484
1614
|
});
|
|
1485
1615
|
}
|
|
1486
1616
|
//#endregion
|
|
@@ -1813,6 +1943,9 @@ window.__ModuleLoader__.load({
|
|
|
1813
1943
|
.fm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}
|
|
1814
1944
|
.fm-plugin-card-chevron-open{transform:rotate(180deg)}
|
|
1815
1945
|
.fm-plugin-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:16px}
|
|
1946
|
+
/* dsh 0.2 Plugins-page bundle configuration: the page draws its own title,
|
|
1947
|
+
icon and crumb, so this surface is body-only — no card border or collapse. */
|
|
1948
|
+
.fm-plugin-page{flex-direction:column;display:flex}
|
|
1816
1949
|
.fm-plugin-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
1817
1950
|
.fm-plugin-field+.fm-plugin-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
1818
1951
|
/* The stats row ends the card body, so it carries NO bottom padding of its
|
|
@@ -2301,13 +2434,16 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2301
2434
|
return snap;
|
|
2302
2435
|
}
|
|
2303
2436
|
/** The session's pending user interaction (question / plan review / approval),
|
|
2304
|
-
* read from the uiSession
|
|
2305
|
-
*
|
|
2306
|
-
*
|
|
2307
|
-
*
|
|
2308
|
-
*
|
|
2437
|
+
* read from the uiSession services. dsh 0.1.2 exposed the root-level
|
|
2438
|
+
* `pendingInteractions` map; dsh 0.2 unified it into `sessionStatus`
|
|
2439
|
+
* (`ReadonlyMap<sessionId, { running, pendingInteraction, completionUnread }>`).
|
|
2440
|
+
* When neither service is present (older dsh), falls back to the session
|
|
2441
|
+
* snapshot's flat `pending` array through the `legacyPendingOf` adapter.
|
|
2442
|
+
* Returns null when this session has nothing pending. */
|
|
2309
2443
|
function usePendingInteraction(uiSession, sessionId, snap) {
|
|
2310
|
-
const
|
|
2444
|
+
const legacyMap = uiSession && uiSession.pendingInteractions;
|
|
2445
|
+
const statusMap = uiSession && uiSession.sessionStatus;
|
|
2446
|
+
const source = legacyMap || statusMap;
|
|
2311
2447
|
const read = () => {
|
|
2312
2448
|
try {
|
|
2313
2449
|
return source ? source.getSnapshot() : null;
|
|
@@ -2328,7 +2464,47 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2328
2464
|
return;
|
|
2329
2465
|
}
|
|
2330
2466
|
}, [source]);
|
|
2331
|
-
return pendingInteractionOf(map, sessionId) || legacyPendingOf(snap);
|
|
2467
|
+
return (legacyMap ? pendingInteractionOf(map, sessionId) : pendingInteractionFromStatus(map, sessionId)) || legacyPendingOf(snap);
|
|
2468
|
+
}
|
|
2469
|
+
/** The viewed session id, resolved across dsh generations. dsh <= 0.1.x carried
|
|
2470
|
+
* the selection on the session-list snapshot (`listState.current`); dsh 0.2
|
|
2471
|
+
* dropped it and publishes the main view's retained session as the uiSession
|
|
2472
|
+
* main binding. Subscribes to whichever source exists (both, when both do), so
|
|
2473
|
+
* switching sessions re-resolves either way. */
|
|
2474
|
+
function useCurrentSessionId(sessions, uiSession, listState) {
|
|
2475
|
+
const read = () => {
|
|
2476
|
+
if (!sessions || typeof sessions.list?.getSnapshot !== "function") return void 0;
|
|
2477
|
+
let mainBinding = null;
|
|
2478
|
+
try {
|
|
2479
|
+
mainBinding = uiSession && uiSession.current ? uiSession.current.getSnapshot() : null;
|
|
2480
|
+
} catch {
|
|
2481
|
+
mainBinding = null;
|
|
2482
|
+
}
|
|
2483
|
+
return currentSessionIdOf(sessions.list.getSnapshot(), mainBinding);
|
|
2484
|
+
};
|
|
2485
|
+
const [id, setId] = (0, react.useState)(read);
|
|
2486
|
+
(0, react.useEffect)(() => {
|
|
2487
|
+
if (!sessions || typeof sessions.list?.subscribe !== "function") {
|
|
2488
|
+
setId(void 0);
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
const sync = () => setId(read());
|
|
2492
|
+
sync();
|
|
2493
|
+
const unsubs = [];
|
|
2494
|
+
try {
|
|
2495
|
+
unsubs.push(sessions.list.subscribe(sync));
|
|
2496
|
+
} catch {}
|
|
2497
|
+
const main = uiSession && uiSession.current;
|
|
2498
|
+
if (main && typeof main.subscribe === "function") try {
|
|
2499
|
+
unsubs.push(main.subscribe(sync));
|
|
2500
|
+
} catch {}
|
|
2501
|
+
return () => {
|
|
2502
|
+
for (const u of unsubs) try {
|
|
2503
|
+
u();
|
|
2504
|
+
} catch {}
|
|
2505
|
+
};
|
|
2506
|
+
}, [sessions, uiSession]);
|
|
2507
|
+
return id;
|
|
2332
2508
|
}
|
|
2333
2509
|
/** The session's Chat view snapshot (dsh 0.1.2 `ChatSnapshot`) from the
|
|
2334
2510
|
* uiConversation assembly — the first subscriber activates the chat target,
|
|
@@ -2355,20 +2531,25 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2355
2531
|
}, [uiConversation, sessionId]);
|
|
2356
2532
|
return chat;
|
|
2357
2533
|
}
|
|
2358
|
-
/** The official "对话显示" preference (`ui-chat.transcriptView
|
|
2359
|
-
*
|
|
2360
|
-
* TranscriptViewPolicy binds
|
|
2361
|
-
*
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2534
|
+
/** The official "对话显示" preference (`ui-chat.transcriptView`), read
|
|
2535
|
+
* reactively. dsh <= 0.1.x serves it through the settings scope the official
|
|
2536
|
+
* TranscriptViewPolicy binds (values normal | compact); dsh 0.2 retired that
|
|
2537
|
+
* service and serves the same field through the `configForms` controller for
|
|
2538
|
+
* the `ui-chat` namespace (compact | standard | detailed | verbose, with the
|
|
2539
|
+
* legacy normal | expanded values still accepted). Both are read through the
|
|
2540
|
+
* shared `transcriptViewOf` projection; only `compact` folds the tool flow.
|
|
2541
|
+
* Defaults to compact when no preference has arrived yet. */
|
|
2542
|
+
function useTranscriptViewMode(settingsScope, configForms) {
|
|
2543
|
+
const form = (0, react.useMemo)(() => {
|
|
2544
|
+
if (settingsScope) return settingsScope;
|
|
2365
2545
|
try {
|
|
2366
|
-
|
|
2367
|
-
return (section ? section.transcriptView : null) === "normal" ? "normal" : "compact";
|
|
2546
|
+
return configForms && typeof configForms.get === "function" ? configForms.get("ui-chat") : null;
|
|
2368
2547
|
} catch {
|
|
2369
|
-
return
|
|
2548
|
+
return null;
|
|
2370
2549
|
}
|
|
2371
|
-
};
|
|
2550
|
+
}, [settingsScope, configForms]);
|
|
2551
|
+
const source = form && typeof form.getSnapshot === "function" ? form : null;
|
|
2552
|
+
const read = () => transcriptViewOf(source);
|
|
2372
2553
|
const [mode, setMode] = (0, react.useState)(read);
|
|
2373
2554
|
(0, react.useEffect)(() => {
|
|
2374
2555
|
if (!source) {
|
|
@@ -2433,13 +2614,13 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2433
2614
|
return seat ? seat.querySelector("[contenteditable=\"true\"]") : null;
|
|
2434
2615
|
}
|
|
2435
2616
|
function FocusContent(props) {
|
|
2436
|
-
const { useSessions, sessions, workspaces, conversation, chatFileMentions, uiSession, uiConversation, settingsScope, t } = props;
|
|
2617
|
+
const { useSessions, sessions, workspaces, conversation, chatFileMentions, uiSession, uiConversation, settingsScope, configForms, sidebarRight, t } = props;
|
|
2437
2618
|
const prefs = usePrefs();
|
|
2438
2619
|
const listState = useSessions((s) => s);
|
|
2439
|
-
const currentId =
|
|
2620
|
+
const currentId = useCurrentSessionId(sessions, uiSession, listState);
|
|
2440
2621
|
const snap = useSessionSnapshot(sessions, currentId);
|
|
2441
2622
|
const chatView = useChatView(uiConversation, currentId);
|
|
2442
|
-
const transcriptView = useTranscriptViewMode(settingsScope);
|
|
2623
|
+
const transcriptView = useTranscriptViewMode(settingsScope, configForms);
|
|
2443
2624
|
const legacy = (0, react.useMemo)(() => legacySliceOf(chatView, snap), [chatView, snap]);
|
|
2444
2625
|
const stable = legacy.partial == null;
|
|
2445
2626
|
const mdLabels = (0, react.useMemo)(() => markdownLabels(t), [t]);
|
|
@@ -2765,6 +2946,24 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2765
2946
|
if (conversation && typeof conversation.resolveImage === "function") return conversation.resolveImage(currentId, attachment);
|
|
2766
2947
|
return Promise.reject(/* @__PURE__ */ new Error("dsh-focus-overlay: image resolver unavailable"));
|
|
2767
2948
|
}
|
|
2949
|
+
const openMentionedFile = (0, react.useCallback)((path, options) => {
|
|
2950
|
+
try {
|
|
2951
|
+
if (workspaces && typeof workspaces.openPath === "function") {
|
|
2952
|
+
workspaces.openPath(path).catch(() => {});
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
const cwd = currentId != null ? sessions?.list?.getSnapshot?.()?.byId?.[currentId]?.cwd : void 0;
|
|
2956
|
+
if (sidebarRight && currentId != null && typeof sidebarRight.openResource === "function") {
|
|
2957
|
+
const address = sessionFileAddressOf(String(currentId), cwd, path);
|
|
2958
|
+
sidebarRight.openResource(address, options && options.line !== void 0 ? { params: { line: options.line } } : void 0);
|
|
2959
|
+
}
|
|
2960
|
+
} catch {}
|
|
2961
|
+
}, [
|
|
2962
|
+
sidebarRight,
|
|
2963
|
+
workspaces,
|
|
2964
|
+
sessions,
|
|
2965
|
+
currentId
|
|
2966
|
+
]);
|
|
2768
2967
|
const fileMentionsFor = (0, react.useCallback)((node) => {
|
|
2769
2968
|
if (!chatFileMentions) return void 0;
|
|
2770
2969
|
try {
|
|
@@ -2774,14 +2973,16 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
2774
2973
|
step: node.step
|
|
2775
2974
|
},
|
|
2776
2975
|
seq: node.seq,
|
|
2777
|
-
openFile: (path) =>
|
|
2778
|
-
|
|
2779
|
-
}
|
|
2780
|
-
});
|
|
2976
|
+
openFile: (path, options) => openMentionedFile(path, options)
|
|
2977
|
+
}, currentId);
|
|
2781
2978
|
} catch {
|
|
2782
2979
|
return;
|
|
2783
2980
|
}
|
|
2784
|
-
}, [
|
|
2981
|
+
}, [
|
|
2982
|
+
chatFileMentions,
|
|
2983
|
+
currentId,
|
|
2984
|
+
openMentionedFile
|
|
2985
|
+
]);
|
|
2785
2986
|
const binding = (0, react.useMemo)(() => sessions && currentId != null ? sessions.binding(currentId) : void 0, [sessions, currentId]);
|
|
2786
2987
|
const inputFace = useInputFace(conversation, binding && binding.ctx);
|
|
2787
2988
|
const inputState = useInputState(inputFace);
|
|
@@ -3037,7 +3238,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3037
3238
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3038
3239
|
className: "fm-process-label",
|
|
3039
3240
|
children: processLabel(it.counts, t)
|
|
3040
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3241
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: "fm-process-chevron" })]
|
|
3041
3242
|
}) : null, open ? it.workItems.map((wi, wi2) => renderItem(wi, key + "-w" + wi2, false)) : null] }, key);
|
|
3042
3243
|
}
|
|
3043
3244
|
return null;
|
|
@@ -3371,6 +3572,15 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3371
3572
|
})
|
|
3372
3573
|
});
|
|
3373
3574
|
}
|
|
3575
|
+
/** The settings body both plugin-settings surfaces share: the preference
|
|
3576
|
+
* fields plus the focus-entry counter. Rendered by the legacy Settings card
|
|
3577
|
+
* and by the dsh 0.2 Plugins-page bundle page. */
|
|
3578
|
+
function FocusSettingsBody({ t }) {
|
|
3579
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusPrefsFields, { t }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusEntryStat, { t })] });
|
|
3580
|
+
}
|
|
3581
|
+
/** Legacy surface (dsh <= 0.1.x): a collapsible `<li>` card in the Settings
|
|
3582
|
+
* → Plugins tab, registered into the keyed `settings.plugin.item` slot. dsh
|
|
3583
|
+
* 0.2 removed that slot, so this only renders where the slot is declared. */
|
|
3374
3584
|
function FocusSettingsCard({ t }) {
|
|
3375
3585
|
const [open, setOpen] = (0, react.useState)(false);
|
|
3376
3586
|
const title = t("settings.label");
|
|
@@ -3391,13 +3601,24 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3391
3601
|
className: "fm-plugin-card-desc",
|
|
3392
3602
|
children: t("settings.description")
|
|
3393
3603
|
})]
|
|
3394
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3395
|
-
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
3604
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDown, { className: "fm-plugin-card-chevron" + (open ? " fm-plugin-card-chevron-open" : "") })]
|
|
3605
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3396
3606
|
className: "fm-plugin-card-body",
|
|
3397
|
-
children:
|
|
3607
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusSettingsBody, { t })
|
|
3398
3608
|
}) : null]
|
|
3399
3609
|
});
|
|
3400
3610
|
}
|
|
3611
|
+
/** dsh 0.2 surface: the bundle's own configuration on the sidebar Plugins
|
|
3612
|
+
* page (`plugins.bundle.config`, keyed by the bundle's package name). The page
|
|
3613
|
+
* draws the title, icon, and crumb itself and always asks for `view: 'page'`,
|
|
3614
|
+
* so this renders the body alone — no card chrome, no collapse. */
|
|
3615
|
+
function FocusBundleConfig({ t, view }) {
|
|
3616
|
+
if (view !== void 0 && view !== "page") return null;
|
|
3617
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3618
|
+
className: "fm-plugin-page",
|
|
3619
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FocusSettingsBody, { t })
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3401
3622
|
function FocusOnboarding(props) {
|
|
3402
3623
|
const { complete, openSection, t } = props;
|
|
3403
3624
|
const [done, setDone] = (0, react.useState)(() => onboardingStore.isDone());
|
|
@@ -3429,7 +3650,13 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3429
3650
|
onboardingStore.markDone();
|
|
3430
3651
|
setDone(true);
|
|
3431
3652
|
finish();
|
|
3432
|
-
|
|
3653
|
+
let opened = false;
|
|
3654
|
+
if (typeof props.openSettings === "function") try {
|
|
3655
|
+
opened = props.openSettings() !== false;
|
|
3656
|
+
} catch {
|
|
3657
|
+
opened = false;
|
|
3658
|
+
}
|
|
3659
|
+
if (!opened && typeof openSection === "function") openSection("plugins");
|
|
3433
3660
|
};
|
|
3434
3661
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
3435
3662
|
open: true,
|
|
@@ -3494,13 +3721,20 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3494
3721
|
/**
|
|
3495
3722
|
* Focus Mode, browser half: a full-screen reading overlay in `shell.overlay`
|
|
3496
3723
|
* (additive, never replaces shipped UI), a "专注" button in the session header
|
|
3497
|
-
* action row, and
|
|
3498
|
-
*
|
|
3499
|
-
*
|
|
3500
|
-
*
|
|
3501
|
-
*
|
|
3724
|
+
* action row, and the plugin's settings card. The settings surface lives where
|
|
3725
|
+
* the running dsh keeps it: the Settings → Plugins tab's keyed
|
|
3726
|
+
* `settings.plugin.item` slot on dsh <= 0.1.x, and the sidebar Plugins page's
|
|
3727
|
+
* `plugins.bundle.config` slot (keyed by this bundle's package name) on dsh
|
|
3728
|
+
* 0.2, which retired both that slot and the `settingsScope` service. Rendering
|
|
3729
|
+
* reuses the official client UI primitives module
|
|
3730
|
+
* (`@deepseek-ai/dsh-client-ui-primitives` runtime id) — Markdown /
|
|
3731
|
+
* projectUserText / Button — plus the conversation service's composer registry
|
|
3732
|
+
* and the uiSession pending-interaction / main-binding service.
|
|
3502
3733
|
*/
|
|
3503
3734
|
const NS = "focus";
|
|
3735
|
+
/** This bundle's package name — the key `plugins.bundle.config` and the
|
|
3736
|
+
* `pluginNavigation.openBundle` deep link address this plugin by (dsh 0.2). */
|
|
3737
|
+
const PACKAGE_NAME = "dsh-focus-overlay";
|
|
3504
3738
|
let warnedLegacy = false;
|
|
3505
3739
|
function warnLegacyOnce() {
|
|
3506
3740
|
if (warnedLegacy) return;
|
|
@@ -3516,8 +3750,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3516
3750
|
"locale",
|
|
3517
3751
|
"workspaces",
|
|
3518
3752
|
"uiSession",
|
|
3519
|
-
"uiConversation"
|
|
3520
|
-
"settingsScope"
|
|
3753
|
+
"uiConversation"
|
|
3521
3754
|
],
|
|
3522
3755
|
apply(ctx) {
|
|
3523
3756
|
const style = document.createElement("style");
|
|
@@ -3550,6 +3783,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3550
3783
|
const workspaces = ctx.workspaces;
|
|
3551
3784
|
const conversation = ctx.get("conversation");
|
|
3552
3785
|
const chatFileMentions = ctx.get("chatFileMentions");
|
|
3786
|
+
const sidebarRight = ctx.get("sidebarRight");
|
|
3553
3787
|
const uiSession = ctx.get("uiSession");
|
|
3554
3788
|
const uiConversation = ctx.get("uiConversation");
|
|
3555
3789
|
if (!uiSession || !uiConversation) warnLegacyOnce();
|
|
@@ -3560,6 +3794,7 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3560
3794
|
} catch {
|
|
3561
3795
|
settingsScope = null;
|
|
3562
3796
|
}
|
|
3797
|
+
const configForms = ctx.get("configForms");
|
|
3563
3798
|
ctx.effect(() => {
|
|
3564
3799
|
let currentId = void 0;
|
|
3565
3800
|
let unsubSession = null;
|
|
@@ -3622,18 +3857,27 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3622
3857
|
} catch {}
|
|
3623
3858
|
if (!unsubChat) chatSlice = legacySliceOf(null, face.getSnapshot());
|
|
3624
3859
|
};
|
|
3860
|
+
const mainBinding = () => {
|
|
3861
|
+
try {
|
|
3862
|
+
return uiSession && uiSession.current ? uiSession.current.getSnapshot() : null;
|
|
3863
|
+
} catch {
|
|
3864
|
+
return null;
|
|
3865
|
+
}
|
|
3866
|
+
};
|
|
3625
3867
|
const onList = () => {
|
|
3626
|
-
const
|
|
3627
|
-
const next = ls ? ls.current : void 0;
|
|
3868
|
+
const next = currentSessionIdOf(sessions.list.getSnapshot(), mainBinding());
|
|
3628
3869
|
if (next !== currentId) {
|
|
3629
3870
|
currentId = next;
|
|
3630
3871
|
watch(next);
|
|
3631
|
-
}
|
|
3872
|
+
} else if (next != null && !unsubSession) watch(next);
|
|
3632
3873
|
};
|
|
3633
3874
|
onList();
|
|
3634
3875
|
const unsubList = sessions.list.subscribe(onList);
|
|
3876
|
+
const mainSource = uiSession && uiSession.current;
|
|
3877
|
+
const unsubMain = mainSource && typeof mainSource.subscribe === "function" ? mainSource.subscribe(onList) : null;
|
|
3635
3878
|
return () => {
|
|
3636
3879
|
if (unsubList) unsubList();
|
|
3880
|
+
if (unsubMain) unsubMain();
|
|
3637
3881
|
if (unsubSession) unsubSession();
|
|
3638
3882
|
if (unsubChat) unsubChat();
|
|
3639
3883
|
};
|
|
@@ -3668,6 +3912,8 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3668
3912
|
uiSession,
|
|
3669
3913
|
uiConversation,
|
|
3670
3914
|
settingsScope,
|
|
3915
|
+
configForms,
|
|
3916
|
+
sidebarRight,
|
|
3671
3917
|
t
|
|
3672
3918
|
})));
|
|
3673
3919
|
ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
|
|
@@ -3682,13 +3928,30 @@ body[data-fm-focus] #root :has(> [data-slot="conversation"]){margin-bottom:0!imp
|
|
|
3682
3928
|
id: "dsh-focus-overlay",
|
|
3683
3929
|
order: 1e3
|
|
3684
3930
|
}, () => (0, react.createElement)(FocusSettingsCard, { t })));
|
|
3931
|
+
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
3932
|
+
name: "plugins.bundle.config",
|
|
3933
|
+
key: PACKAGE_NAME
|
|
3934
|
+
}, (props) => (0, react.createElement)(FocusBundleConfig, {
|
|
3935
|
+
...props,
|
|
3936
|
+
t
|
|
3937
|
+
})));
|
|
3938
|
+
const pluginNavigation = ctx.get("pluginNavigation");
|
|
3939
|
+
const openSettings = pluginNavigation && typeof pluginNavigation.openBundle === "function" ? () => {
|
|
3940
|
+
try {
|
|
3941
|
+
pluginNavigation.openBundle(PACKAGE_NAME);
|
|
3942
|
+
return true;
|
|
3943
|
+
} catch {
|
|
3944
|
+
return false;
|
|
3945
|
+
}
|
|
3946
|
+
} : void 0;
|
|
3685
3947
|
ctx.slots.inject("settings.onboarding", () => ctx.slots.register({
|
|
3686
3948
|
name: "settings.onboarding",
|
|
3687
3949
|
id: "dsh-focus-overlay",
|
|
3688
3950
|
order: 10
|
|
3689
3951
|
}, (props) => (0, react.createElement)(FocusOnboarding, {
|
|
3690
3952
|
...props,
|
|
3691
|
-
t
|
|
3953
|
+
t,
|
|
3954
|
+
openSettings
|
|
3692
3955
|
})));
|
|
3693
3956
|
}
|
|
3694
3957
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-focus-overlay",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
4
4
|
"description": "Focus Mode for the dsh web GUI: a full-screen reading overlay that hides chrome and folds the AI tool-call flow into summaries, reusing the official Markdown / image primitives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.mjs",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"0.1.2-alpha.5": "compatible",
|
|
32
32
|
"0.1.2-rc.1": "compatible",
|
|
33
33
|
"0.1.3-alpha.1": "compatible",
|
|
34
|
-
"0.1.5-rc.1": "compatible"
|
|
34
|
+
"0.1.5-rc.1": "compatible",
|
|
35
|
+
"0.2.0-rc.2": "compatible"
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
},
|