dsh-width-tiers 1.0.0 → 1.0.2
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 +16 -0
- package/README.zh.md +14 -0
- package/lib/client.js +211 -30
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -47,6 +47,11 @@ Restart `dsh web` (the plugin loads at boot) and hard-refresh the page
|
|
|
47
47
|
(Ctrl+Shift+R). A round button with five bars appears at the bottom-right.
|
|
48
48
|
|
|
49
49
|
> Installing from a local checkout: `dsh plugin --profile web add file:/path/to/dsh-width-tiers`.
|
|
50
|
+
> Local-development gotcha: `file:` dependencies are **copied** into the
|
|
51
|
+
> profile's `node_modules` (pnpm does not symlink them). After editing the
|
|
52
|
+
> plugin source, re-sync the copy (e.g. `rsync -a --delete --exclude .git
|
|
53
|
+
> /path/to/dsh-width-tiers/ ~/.dsh/profiles/web/node_modules/dsh-width-tiers/`)
|
|
54
|
+
> or re-install after removing it, then restart `dsh web` and hard-refresh.
|
|
50
55
|
|
|
51
56
|
## Usage
|
|
52
57
|
|
|
@@ -57,6 +62,14 @@ Restart `dsh web` (the plugin loads at boot) and hard-refresh the page
|
|
|
57
62
|
- Non-standard tiers also close the details panel (re-closed automatically if
|
|
58
63
|
reopened); `standard` leaves everything to the app.
|
|
59
64
|
|
|
65
|
+
## Language
|
|
66
|
+
|
|
67
|
+
All picker copy (tier labels, button `aria-label` and title) follows the
|
|
68
|
+
app's **Settings → Language** (中文 / English), through the app's own locale
|
|
69
|
+
service. Switching the language re-renders the button and menu immediately;
|
|
70
|
+
when no explicit language is chosen the app falls back to the browser's
|
|
71
|
+
language, and Chinese is the last resort. No plugin-side setting needed.
|
|
72
|
+
|
|
60
73
|
## How it works
|
|
61
74
|
|
|
62
75
|
- The element that defines `--dsh-chat-content-width` is **located at
|
|
@@ -67,6 +80,9 @@ Restart `dsh web` (the plugin loads at boot) and hard-refresh the page
|
|
|
67
80
|
simulating clicks.
|
|
68
81
|
- The sidebar is only ever collapsed by the `full` tier; returning to
|
|
69
82
|
`standard` restores it only if this plugin collapsed it.
|
|
83
|
+
- Dictionaries are registered with the app's **locale service**
|
|
84
|
+
(`ctx.locale.register` / `bind`) and re-rendered through its change
|
|
85
|
+
subscription, so the copy always matches the app's active language.
|
|
70
86
|
|
|
71
87
|
## Limitations
|
|
72
88
|
|
package/README.zh.md
CHANGED
|
@@ -43,6 +43,11 @@ dsh plugin --profile web add dsh-width-tiers
|
|
|
43
43
|
右下角会出现五条横条的圆形按钮。
|
|
44
44
|
|
|
45
45
|
> 本地目录安装:`dsh plugin --profile web add file:/path/to/dsh-width-tiers`。
|
|
46
|
+
> 本地开发注意:`file:` 依赖会被 **拷贝** 进 profile 的 node_modules(pnpm
|
|
47
|
+
> 不会建软链)。改完插件源码后,需要重新同步拷贝(例如 `rsync -a --delete
|
|
48
|
+
> --exclude .git /path/to/dsh-width-tiers/
|
|
49
|
+
> ~/.dsh/profiles/web/node_modules/dsh-width-tiers/`)或先 remove 再 add,
|
|
50
|
+
> 然后重启 `dsh web` 并硬刷新。
|
|
46
51
|
|
|
47
52
|
## 使用
|
|
48
53
|
|
|
@@ -51,6 +56,13 @@ dsh plugin --profile web add dsh-width-tiers
|
|
|
51
56
|
随时点轨道图标展开,展开后保持展开。
|
|
52
57
|
- 非标准档会关闭详情面板(被重新打开会自动再关);标准档完全交给应用。
|
|
53
58
|
|
|
59
|
+
## 多语言
|
|
60
|
+
|
|
61
|
+
选择器里的所有文案(档位名、按钮的 `aria-label` 和标题)跟随应用的
|
|
62
|
+
**设置 → 语言**(中文 / English),通过应用自身的 locale 服务实现。切换
|
|
63
|
+
语言后按钮和菜单会立即重渲染;未显式选择语言时,应用会回退到浏览器语言,
|
|
64
|
+
最后兜底中文。无需插件侧单独设置。
|
|
65
|
+
|
|
54
66
|
## 原理
|
|
55
67
|
|
|
56
68
|
- 运行时**自动定位**定义 `--dsh-chat-content-width` 的元素(第一个计算值
|
|
@@ -58,6 +70,8 @@ dsh plugin --profile web add dsh-width-tiers
|
|
|
58
70
|
- 面板操作走 **layout 服务**(`ctx.layout`),从不模拟点击。
|
|
59
71
|
- 侧边栏只被「全宽」档收起;回到「标准」时,只有本插件自己收起的侧边栏
|
|
60
72
|
才会被恢复展开(你手动收的不动)。
|
|
73
|
+
- 字典注册到应用的 **locale 服务**(`ctx.locale.register` / `bind`),并
|
|
74
|
+
订阅其变更事件重渲染,文案始终与应用的当前语言一致。
|
|
61
75
|
|
|
62
76
|
## 已知限制
|
|
63
77
|
|
package/lib/client.js
CHANGED
|
@@ -35,18 +35,56 @@ window.__ModuleLoader__.load({
|
|
|
35
35
|
* active); `standard` leaves everything to the app.
|
|
36
36
|
* - The chosen tier persists in localStorage and is restored on the
|
|
37
37
|
* next visit.
|
|
38
|
+
* - All copy is localized through the app's locale service: tier
|
|
39
|
+
* labels and the button's aria/title follow Settings → Language
|
|
40
|
+
* (zh / en, browser-derived fallback) and re-render on switch.
|
|
38
41
|
*/
|
|
39
42
|
const KEY = "dsh.widthTier";
|
|
40
43
|
const OLD_KEY = "dsh.wide";
|
|
41
|
-
const SELF_HEAL_MS = 1000;
|
|
42
44
|
const READY_TIMEOUT_MS = 30000;
|
|
45
|
+
/* Self-heal cadence: DOM-driven (MutationObserver scheduling on the
|
|
46
|
+
* next animation frame — before paint, so the width never flashes
|
|
47
|
+
* default) with a low-frequency fallback and backoff retries while
|
|
48
|
+
* the width root is missing (conversation-switch remount). */
|
|
49
|
+
const ROOT_RETRY_MIN_MS = 200;
|
|
50
|
+
const ROOT_RETRY_MAX_MS = 2000;
|
|
51
|
+
const FALLBACK_HEAL_MS = 2000;
|
|
52
|
+
|
|
53
|
+
/** Dictionary namespace owned by this plugin. */
|
|
54
|
+
const NS = "width-tiers";
|
|
55
|
+
|
|
56
|
+
//#region lib/types/client/locales.js
|
|
57
|
+
/** `width-tiers` namespace dictionaries (tier labels and picker copy). */
|
|
58
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
59
|
+
const zh = {
|
|
60
|
+
"tier.standard": "标准",
|
|
61
|
+
"tier.medium": "中等",
|
|
62
|
+
"tier.wide": "宽",
|
|
63
|
+
"tier.ultra": "超宽",
|
|
64
|
+
"tier.full": "全宽",
|
|
65
|
+
"button.aria": "对话区宽度档位:{label}({value}),点击选择档位",
|
|
66
|
+
"button.title.summary": "对话区宽度档位:{label}({value})",
|
|
67
|
+
"button.title.hint": "点击选择:{list}"
|
|
68
|
+
};
|
|
69
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
70
|
+
const en = {
|
|
71
|
+
"tier.standard": "Standard",
|
|
72
|
+
"tier.medium": "Medium",
|
|
73
|
+
"tier.wide": "Wide",
|
|
74
|
+
"tier.ultra": "Ultra",
|
|
75
|
+
"tier.full": "Full",
|
|
76
|
+
"button.aria": "Chat width tier: {label} ({value}). Click to choose a tier.",
|
|
77
|
+
"button.title.summary": "Chat width tier: {label} ({value})",
|
|
78
|
+
"button.title.hint": "Click to choose: {list}"
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
43
81
|
|
|
44
82
|
const TIERS = [
|
|
45
|
-
{ id: "standard",
|
|
46
|
-
{ id: "medium",
|
|
47
|
-
{ id: "wide",
|
|
48
|
-
{ id: "ultra",
|
|
49
|
-
{ id: "full",
|
|
83
|
+
{ id: "standard", key: "tier.standard", value: "748px" },
|
|
84
|
+
{ id: "medium", key: "tier.medium", value: "960px" },
|
|
85
|
+
{ id: "wide", key: "tier.wide", value: "1280px" },
|
|
86
|
+
{ id: "ultra", key: "tier.ultra", value: "1400px" },
|
|
87
|
+
{ id: "full", key: "tier.full", value: "100%" }
|
|
50
88
|
];
|
|
51
89
|
|
|
52
90
|
function tierOf(id) {
|
|
@@ -76,14 +114,18 @@ window.__ModuleLoader__.load({
|
|
|
76
114
|
//#endregion
|
|
77
115
|
|
|
78
116
|
/** Services required by the browser bundle. */
|
|
79
|
-
const inject = ["layout"];
|
|
117
|
+
const inject = ["layout", "locale"];
|
|
80
118
|
|
|
81
119
|
/**
|
|
82
120
|
* Client plugin body: inject the styles, build the picker button and
|
|
83
121
|
* menu, and drive the width tiers through the layout service.
|
|
84
|
-
* @param ctx - client root context (layout injected).
|
|
122
|
+
* @param ctx - client root context (layout + locale injected).
|
|
85
123
|
*/
|
|
86
124
|
function apply(ctx) {
|
|
125
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
126
|
+
zh,
|
|
127
|
+
en
|
|
128
|
+
}), "dsh-width-tiers: dictionaries");
|
|
87
129
|
ctx.effect(() => {
|
|
88
130
|
if (typeof document === "undefined") return;
|
|
89
131
|
|
|
@@ -93,6 +135,7 @@ window.__ModuleLoader__.load({
|
|
|
93
135
|
document.head.appendChild(styleTag);
|
|
94
136
|
|
|
95
137
|
const layout = ctx.layout;
|
|
138
|
+
const t = ctx.locale.bind(NS);
|
|
96
139
|
|
|
97
140
|
/* ---- helpers ---- */
|
|
98
141
|
const body = () => document.body;
|
|
@@ -137,25 +180,62 @@ window.__ModuleLoader__.load({
|
|
|
137
180
|
|
|
138
181
|
/* Locate the element that defines --dsh-chat-content-width at
|
|
139
182
|
* runtime (first match wins; only one element declares it).
|
|
140
|
-
* Cached;
|
|
183
|
+
* Cached; the app remounts the chat tree on conversation switch,
|
|
184
|
+
* so re-location first walks the recorded child-index path from
|
|
185
|
+
* <body> (React keeps the tree shape — O(depth), one style
|
|
186
|
+
* read) and only falls back to a full scan. */
|
|
141
187
|
let widthRoot = null;
|
|
188
|
+
let widthPath = null;
|
|
142
189
|
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
190
|
+
function hasWidthVar(el) {
|
|
191
|
+
return getComputedStyle(el)
|
|
192
|
+
.getPropertyValue("--dsh-chat-content-width")
|
|
193
|
+
.trim() !== "";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function recordWidthPath(el) {
|
|
197
|
+
const path = [];
|
|
198
|
+
let node = el;
|
|
199
|
+
while (node && node !== document.body) {
|
|
200
|
+
const parent = node.parentNode;
|
|
201
|
+
if (!parent || !parent.children) break;
|
|
202
|
+
path.unshift(Array.prototype.indexOf.call(parent.children, node));
|
|
203
|
+
node = parent;
|
|
204
|
+
}
|
|
205
|
+
widthPath = path.length > 0 ? path : null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function scanWidthRoot() {
|
|
146
209
|
const all = document.querySelectorAll("[class]");
|
|
147
210
|
for (let i = 0; i < all.length; i++) {
|
|
148
|
-
|
|
149
|
-
.getPropertyValue("--dsh-chat-content-width")
|
|
150
|
-
.trim();
|
|
151
|
-
if (v) {
|
|
211
|
+
if (hasWidthVar(all[i])) {
|
|
152
212
|
widthRoot = all[i];
|
|
213
|
+
recordWidthPath(widthRoot);
|
|
153
214
|
return widthRoot;
|
|
154
215
|
}
|
|
155
216
|
}
|
|
217
|
+
widthRoot = null;
|
|
218
|
+
widthPath = null;
|
|
156
219
|
return null;
|
|
157
220
|
}
|
|
158
221
|
|
|
222
|
+
function findWidthRoot(allowScan) {
|
|
223
|
+
if (widthRoot !== null && document.contains(widthRoot)) return widthRoot;
|
|
224
|
+
if (widthPath !== null) {
|
|
225
|
+
let el = document.body;
|
|
226
|
+
for (let i = 0; i < widthPath.length; i++) {
|
|
227
|
+
el = el.children[widthPath[i]];
|
|
228
|
+
if (!el) break;
|
|
229
|
+
}
|
|
230
|
+
if (el && el !== widthRoot && document.contains(el) && hasWidthVar(el)) {
|
|
231
|
+
widthRoot = el;
|
|
232
|
+
return widthRoot;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (allowScan === false) return null;
|
|
236
|
+
return scanWidthRoot();
|
|
237
|
+
}
|
|
238
|
+
|
|
159
239
|
function tierWidth(tier) {
|
|
160
240
|
switch (tier) {
|
|
161
241
|
case "medium": return "960px";
|
|
@@ -227,13 +307,16 @@ window.__ModuleLoader__.load({
|
|
|
227
307
|
|
|
228
308
|
function syncButton() {
|
|
229
309
|
const tier = currentTier();
|
|
230
|
-
const
|
|
310
|
+
const def = tierOf(tier);
|
|
311
|
+
const label = t(def.key);
|
|
312
|
+
const summary = t("button.title.summary", { label, value: def.value });
|
|
313
|
+
const hint = t("button.title.hint", {
|
|
314
|
+
list: TIERS.map((tierDef) => `${t(tierDef.key)} ${tierDef.value}`).join(" / ")
|
|
315
|
+
});
|
|
231
316
|
btn.innerHTML = barIcon(tier);
|
|
232
317
|
btn.classList.toggle("active", tier !== "standard");
|
|
233
|
-
btn.setAttribute("aria-label",
|
|
234
|
-
btn.title =
|
|
235
|
-
`对话区宽度档位:${t.label}(${t.value})\n` +
|
|
236
|
-
"点击选择:标准 748px / 中等 960px / 宽 1280px / 超宽 1400px / 全宽 100%";
|
|
318
|
+
btn.setAttribute("aria-label", t("button.aria", { label, value: def.value }));
|
|
319
|
+
btn.title = `${summary}\n${hint}`;
|
|
237
320
|
}
|
|
238
321
|
|
|
239
322
|
/* ---- tier menu ---- */
|
|
@@ -242,15 +325,15 @@ window.__ModuleLoader__.load({
|
|
|
242
325
|
menu.setAttribute("role", "menu");
|
|
243
326
|
menu.hidden = true;
|
|
244
327
|
|
|
245
|
-
for (const
|
|
328
|
+
for (const def of TIERS) {
|
|
246
329
|
const item = document.createElement("button");
|
|
247
330
|
item.type = "button";
|
|
248
331
|
item.setAttribute("role", "menuitemradio");
|
|
249
|
-
item.setAttribute("data-tier",
|
|
250
|
-
item.innerHTML = `<span class="dshwm-label">${t.
|
|
332
|
+
item.setAttribute("data-tier", def.id);
|
|
333
|
+
item.innerHTML = `<span class="dshwm-label">${t(def.key)}</span><span class="dshwm-value">${def.value}</span>`;
|
|
251
334
|
item.addEventListener("click", (e) => {
|
|
252
335
|
if (e && e.stopPropagation) e.stopPropagation();
|
|
253
|
-
applyTier(
|
|
336
|
+
applyTier(def.id);
|
|
254
337
|
hideMenu();
|
|
255
338
|
});
|
|
256
339
|
menu.appendChild(item);
|
|
@@ -263,6 +346,8 @@ window.__ModuleLoader__.load({
|
|
|
263
346
|
const on = items[i].getAttribute("data-tier") === tier;
|
|
264
347
|
items[i].classList.toggle("selected", on);
|
|
265
348
|
items[i].setAttribute("aria-checked", String(on));
|
|
349
|
+
const labelEl = items[i].querySelector(".dshwm-label");
|
|
350
|
+
if (labelEl) labelEl.textContent = t(tierOf(items[i].getAttribute("data-tier")).key);
|
|
266
351
|
}
|
|
267
352
|
}
|
|
268
353
|
|
|
@@ -301,6 +386,13 @@ window.__ModuleLoader__.load({
|
|
|
301
386
|
window.setTimeout(() => btn.classList.remove("pulse"), 700);
|
|
302
387
|
}
|
|
303
388
|
|
|
389
|
+
/* ---- re-render localized copy when the app language changes ---- */
|
|
390
|
+
const renderTexts = () => {
|
|
391
|
+
syncButton();
|
|
392
|
+
syncMenu();
|
|
393
|
+
};
|
|
394
|
+
const unsubscribeLocale = ctx.locale.subscribe(renderTexts);
|
|
395
|
+
|
|
304
396
|
/* ---- attach once the app shell has mounted ---- */
|
|
305
397
|
let attached = false;
|
|
306
398
|
|
|
@@ -318,6 +410,7 @@ window.__ModuleLoader__.load({
|
|
|
318
410
|
syncButton();
|
|
319
411
|
const saved = getSavedTier();
|
|
320
412
|
if (saved !== "standard") applyTier(saved);
|
|
413
|
+
startHealing();
|
|
321
414
|
}
|
|
322
415
|
|
|
323
416
|
const readyTimer = setInterval(attach, 300);
|
|
@@ -329,19 +422,107 @@ window.__ModuleLoader__.load({
|
|
|
329
422
|
|
|
330
423
|
/* ---- self-heal: keep the details panel closed for non-standard
|
|
331
424
|
* tiers. The sidebar is never managed here — the user may expand
|
|
332
|
-
* it any time.
|
|
333
|
-
|
|
425
|
+
* it any time. Driven by DOM mutations, scheduled on the next
|
|
426
|
+
* animation frame (before the browser paints) instead of a fixed
|
|
427
|
+
* hot loop: conversation switches remount the chat tree, and the
|
|
428
|
+
* width override must land on the fresh root before its first
|
|
429
|
+
* paint — otherwise the default width flashes for a frame.
|
|
430
|
+
* While the new root has not appeared yet (mid-remount), full
|
|
431
|
+
* scans are owned by a backoff retry; mutation-driven heals only
|
|
432
|
+
* try the cheap path walk so DOM churn cannot storm. ---- */
|
|
433
|
+
let rootMissing = false;
|
|
434
|
+
let rootRetryDelay = ROOT_RETRY_MIN_MS;
|
|
435
|
+
let rootRetryTimer = null;
|
|
436
|
+
let healFrame = null;
|
|
437
|
+
let observer = null;
|
|
438
|
+
let fallbackTimer = null;
|
|
439
|
+
|
|
440
|
+
const scheduleFrame = typeof requestAnimationFrame === "function"
|
|
441
|
+
? (cb) => { healFrame = requestAnimationFrame(cb); }
|
|
442
|
+
: (cb) => { healFrame = window.setTimeout(cb, 0); };
|
|
443
|
+
const cancelFrame = typeof cancelAnimationFrame === "function"
|
|
444
|
+
? (id) => cancelAnimationFrame(id)
|
|
445
|
+
: (id) => window.clearTimeout(id);
|
|
446
|
+
|
|
447
|
+
function scheduleRootRetry() {
|
|
448
|
+
if (rootRetryTimer !== null) return;
|
|
449
|
+
rootRetryTimer = window.setTimeout(() => {
|
|
450
|
+
rootRetryTimer = null;
|
|
451
|
+
const tier = currentTier();
|
|
452
|
+
if (tier === "standard") return;
|
|
453
|
+
const prev = widthRoot;
|
|
454
|
+
const root = findWidthRoot();
|
|
455
|
+
if (root === null) {
|
|
456
|
+
rootRetryDelay = Math.min(rootRetryDelay * 2, ROOT_RETRY_MAX_MS);
|
|
457
|
+
scheduleRootRetry();
|
|
458
|
+
} else {
|
|
459
|
+
rootMissing = false;
|
|
460
|
+
rootRetryDelay = ROOT_RETRY_MIN_MS;
|
|
461
|
+
if (root !== prev) applyContentWidth(tier);
|
|
462
|
+
}
|
|
463
|
+
}, rootRetryDelay);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function heal(fromObserver) {
|
|
334
467
|
const tier = currentTier();
|
|
335
468
|
if (tier === "standard") return;
|
|
336
469
|
closeDetails();
|
|
337
|
-
|
|
338
|
-
|
|
470
|
+
const prev = widthRoot;
|
|
471
|
+
let root;
|
|
472
|
+
if (rootMissing) {
|
|
473
|
+
/* Mid-remount: a mutation-driven heal only walks the
|
|
474
|
+
* recorded path (O(depth)) — the backoff retry owns
|
|
475
|
+
* full scans until the new root exists. */
|
|
476
|
+
root = fromObserver ? findWidthRoot(false) : findWidthRoot();
|
|
477
|
+
} else {
|
|
478
|
+
root = findWidthRoot();
|
|
479
|
+
}
|
|
480
|
+
if (root === null) {
|
|
481
|
+
if (!rootMissing) {
|
|
482
|
+
rootMissing = true;
|
|
483
|
+
rootRetryDelay = ROOT_RETRY_MIN_MS;
|
|
484
|
+
scheduleRootRetry();
|
|
485
|
+
}
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (rootMissing) {
|
|
489
|
+
rootMissing = false;
|
|
490
|
+
rootRetryDelay = ROOT_RETRY_MIN_MS;
|
|
491
|
+
}
|
|
492
|
+
if (root !== prev) applyContentWidth(tier);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function startHealing() {
|
|
496
|
+
if (typeof MutationObserver !== "undefined") {
|
|
497
|
+
observer = new MutationObserver(() => {
|
|
498
|
+
if (healFrame !== null) return;
|
|
499
|
+
scheduleFrame(() => {
|
|
500
|
+
healFrame = null;
|
|
501
|
+
heal(true);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
observer.observe(document.body, {
|
|
505
|
+
subtree: true,
|
|
506
|
+
childList: true,
|
|
507
|
+
attributes: true,
|
|
508
|
+
attributeFilter: ["data-details-collapsed", "data-sidebar-collapsed"]
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
/* Low-frequency fallback so self-heal still runs if the
|
|
512
|
+
* observer misses something — cheap: no scan while the
|
|
513
|
+
* cached root is alive. */
|
|
514
|
+
fallbackTimer = setInterval(() => heal(false), FALLBACK_HEAL_MS);
|
|
515
|
+
}
|
|
339
516
|
|
|
340
517
|
/* ---- disposer ---- */
|
|
341
518
|
return () => {
|
|
519
|
+
unsubscribeLocale();
|
|
342
520
|
clearInterval(readyTimer);
|
|
343
|
-
clearInterval(
|
|
521
|
+
clearInterval(fallbackTimer);
|
|
344
522
|
clearTimeout(timeout);
|
|
523
|
+
clearTimeout(rootRetryTimer);
|
|
524
|
+
if (healFrame !== null) cancelFrame(healFrame);
|
|
525
|
+
if (observer !== null) observer.disconnect();
|
|
345
526
|
document.removeEventListener("click", onDocClick);
|
|
346
527
|
document.removeEventListener("keydown", onDocKey);
|
|
347
528
|
if (btn.parentNode) btn.parentNode.removeChild(btn);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-width-tiers",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "DSH Web client plugin: chat content width tiers (standard / medium / wide / ultra / full) with a floating picker button. Frees wide tables from the app's hard-coded 748px content cap.",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "DSH Web client plugin: chat content width tiers (standard / medium / wide / ultra / full) with a floating picker button. Frees wide tables from the app's hard-coded 748px content cap. UI copy follows the app language (zh / en).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "aaronlei",
|
|
7
7
|
"publishConfig": {
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"client": {
|
|
25
25
|
"platform": "web",
|
|
26
26
|
"inject": [
|
|
27
|
-
"@deepseek-ai/dsh-client-ui-layout"
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
28
|
+
"@deepseek-ai/dsh-client-locale"
|
|
28
29
|
]
|
|
29
30
|
}
|
|
30
31
|
},
|