dsh-width-tiers 1.0.7 → 1.0.8
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 +18 -6
- package/README.zh.md +14 -5
- package/lib/client.js +258 -57
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,9 +28,13 @@ budget" — so the built-in drag handles keep working:
|
|
|
28
28
|
| full 全宽 | column minus the handle budget (fills the available width) | collapsed to the icon rail |
|
|
29
29
|
| custom 自定义 | your dragged width, re-selectable from the menu | untouched |
|
|
30
30
|
|
|
31
|
-
The menu and the button title
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
The menu and the button title show nominal px for the fixed tiers (medium
|
|
32
|
+
1120px, wide 1280px, ultra 1400px). Standard shows the app default px it
|
|
33
|
+
resolves right now (`clamp(680px, column × 0.64, 920px)`, e.g. 710px); full
|
|
34
|
+
shows the live maximum (`column − 176px`, e.g. 934px); custom shows its
|
|
35
|
+
recorded width or "Not set". The app still clamps the applied width to
|
|
36
|
+
"column − 176px"; on a narrow window a higher tier therefore behaves like
|
|
37
|
+
"full".
|
|
34
38
|
|
|
35
39
|
On narrow windows wider tiers are clamped to the available column, so nothing
|
|
36
40
|
overflows under the sidebar.
|
|
@@ -41,6 +45,12 @@ a glance (all lit for custom). Dragging one of the app's native edge handles
|
|
|
41
45
|
switches the picker to the custom tier — the plugin stands aside for native
|
|
42
46
|
drags.
|
|
43
47
|
|
|
48
|
+
Before the first drag, picking custom does not silently keep a width: a guide
|
|
49
|
+
appears at the conversation's right edge and highlights the width handle.
|
|
50
|
+
While dragging, a bubble shows the live transcript width; releasing records it
|
|
51
|
+
as the custom value. Every later handle drag opens the same highlight + live
|
|
52
|
+
readout, no matter which tier was active.
|
|
53
|
+
|
|
44
54
|
## Install
|
|
45
55
|
|
|
46
56
|
```bash
|
|
@@ -95,10 +105,12 @@ language, and Chinese is the last resort. No plugin-side setting needed.
|
|
|
95
105
|
## Limitations
|
|
96
106
|
|
|
97
107
|
- Content width cannot exceed the window: on a narrow window, wider tiers are
|
|
98
|
-
capped by the available column (the
|
|
99
|
-
|
|
108
|
+
capped by the available column (the picker keeps showing the nominal value;
|
|
109
|
+
the live bubble during a drag shows the applied width). Use `full` (which
|
|
110
|
+
also collapses the sidebar) for the maximum.
|
|
100
111
|
- Requires the DSH Web client plugin system (dsh ≥ 0.1.0-rc.x with a `web`
|
|
101
|
-
profile). Verified on dsh 0.1.7-alpha.1 and 0.2.0-rc.2.
|
|
112
|
+
profile). Verified on dsh 0.1.7-alpha.1 and 0.2.0-rc.2; the 0.2.1-rc.1
|
|
113
|
+
width axis matches by bundle diff.
|
|
102
114
|
|
|
103
115
|
## Uninstall
|
|
104
116
|
|
package/README.zh.md
CHANGED
|
@@ -25,8 +25,11 @@ DSH Web 把消息列——包括里面的所有表格——的宽度限制在
|
|
|
25
25
|
| 全宽 | 列宽减手柄预留(占满可用宽度) | 收起为图标轨道 |
|
|
26
26
|
| 自定义 | 拖拽原生手柄得出,可在菜单回切 | 不动 |
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
菜单与按钮标题中:中等 / 宽 / 超宽显示**固定标称值**(1120 / 1280 / 1400px),
|
|
29
|
+
标准显示应用当前默认值(`clamp(680px, 列宽 × 0.64, 920px)`,如 710px),全宽
|
|
30
|
+
显示当前列宽下的最大实际值(列宽 − 176px,如 934px),自定义显示已记录的
|
|
31
|
+
宽度或「未设置」。实际生效宽度仍由应用按「列宽 − 176px」钳制;窄窗口下
|
|
32
|
+
选定较高档位时,效果等同于「全宽」。
|
|
30
33
|
|
|
31
34
|
窄窗口下超宽档位会自动被钳到可用宽度,不会溢出被侧栏覆盖。
|
|
32
35
|
|
|
@@ -34,6 +37,10 @@ DSH Web 把消息列——包括里面的所有表格——的宽度限制在
|
|
|
34
37
|
的五条横条图标也直观显示当前档位(自定义档全亮)。拖动应用自带的
|
|
35
38
|
对话区边缘手柄会自动切到「自定义」档,插件让位于原生拖拽。
|
|
36
39
|
|
|
40
|
+
若从未拖拽过,点击「自定义」不会静默套用某个宽度:右侧对话边缘会出现
|
|
41
|
+
拖拽引导,手柄高亮;拖拽时气泡实时显示对话列宽度,松开后记为该档的值。
|
|
42
|
+
之后每次拖拽手柄(无论从哪个档位开始)都会重新高亮手柄并显示实时宽度。
|
|
43
|
+
|
|
37
44
|
## 安装
|
|
38
45
|
|
|
39
46
|
```bash
|
|
@@ -80,10 +87,12 @@ bundle patch 会自动插入 `dsh-width-tiers` 加载项——无需手动改
|
|
|
80
87
|
|
|
81
88
|
## 已知限制
|
|
82
89
|
|
|
83
|
-
-
|
|
84
|
-
|
|
90
|
+
- 内容宽度不可能超过窗口:窗口窄时,高档位会被可用列宽截断(选择器仍显示
|
|
91
|
+
标称值,实际宽度以拖拽气泡/窗口效果为准);想要最大空间请用「全宽」
|
|
92
|
+
(同时收起侧边栏)。
|
|
85
93
|
- 依赖 DSH Web 客户端插件体系(带 web profile 的 dsh ≥ 0.1.0-rc.x)。
|
|
86
|
-
已在 dsh 0.1.7-alpha.1
|
|
94
|
+
已在 dsh 0.1.7-alpha.1、0.2.0-rc.2 上验证;0.2.1-rc.1 的宽度轴经 bundle
|
|
95
|
+
对比确认一致。
|
|
87
96
|
|
|
88
97
|
## 卸载
|
|
89
98
|
|
package/lib/client.js
CHANGED
|
@@ -29,11 +29,17 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
* 全宽 full column max (sidebar collapsed to the icon rail)
|
|
30
30
|
* 自定义 custom user-dragged width (plugin hands width over to
|
|
31
31
|
* the app's native handles; the dragged value stays
|
|
32
|
-
* re-selectable from the menu)
|
|
32
|
+
* re-selectable from the menu). Before the first
|
|
33
|
+
* drag, picking it opens a guide at the right edge
|
|
34
|
+
* and the live column width is shown while dragging.
|
|
33
35
|
*
|
|
34
|
-
* The menu and title
|
|
35
|
-
*
|
|
36
|
-
*
|
|
36
|
+
* The menu and title show fixed px for medium/wide/ultra
|
|
37
|
+
* (1120/1280/1400), the live app default px for standard, the live
|
|
38
|
+
* maximum (column − 176) for full, and the recorded drag width or
|
|
39
|
+
* "未设置" for custom. The app still clamps every applied px to
|
|
40
|
+
* column − 176. Every handle press (not just the first custom one)
|
|
41
|
+
* opens the live width readout, so repeat drags keep the highlight
|
|
42
|
+
* and the px bubble.
|
|
37
43
|
*
|
|
38
44
|
* - Panels are driven through the layout service (ctx.layout), never
|
|
39
45
|
* by simulating clicks.
|
|
@@ -59,9 +65,10 @@ window.__ModuleLoader__.load({
|
|
|
59
65
|
* the horizontal budget both width handles + safe edges reserve. */
|
|
60
66
|
const CONTENT_MIN_PX = 640;
|
|
61
67
|
const CONTENT_EDGE_BUDGET_PX = 176;
|
|
62
|
-
/*
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
/* Nominal content widths of the fixed tiers (px). */
|
|
69
|
+
const NOMINAL_WIDTH_PX = { medium: 1120, wide: 1280, ultra: 1400 };
|
|
70
|
+
/* The app's no-preference formula: clamp(680px, column × 0.64, 920px).
|
|
71
|
+
* Used as the last-resort live readout before the first drag. */
|
|
65
72
|
const DEFAULT_MIN_PX = 680;
|
|
66
73
|
const DEFAULT_MAX_PX = 920;
|
|
67
74
|
const DEFAULT_COLUMN_RATIO = 0.64;
|
|
@@ -87,6 +94,8 @@ window.__ModuleLoader__.load({
|
|
|
87
94
|
"tier.ultra": "超宽",
|
|
88
95
|
"tier.full": "全宽",
|
|
89
96
|
"tier.custom": "自定义",
|
|
97
|
+
"value.custom.unset": "未设置",
|
|
98
|
+
"guide.hint": "拖拽右侧边缘调整对话宽度",
|
|
90
99
|
"button.aria": "对话区宽度档位:{label}({value}),点击选择档位",
|
|
91
100
|
"button.title.summary": "对话区宽度档位:{label}({value})",
|
|
92
101
|
"button.title.hint": "点击选择:{list}"
|
|
@@ -99,22 +108,25 @@ window.__ModuleLoader__.load({
|
|
|
99
108
|
"tier.ultra": "Ultra",
|
|
100
109
|
"tier.full": "Full",
|
|
101
110
|
"tier.custom": "Custom",
|
|
111
|
+
"value.custom.unset": "Not set",
|
|
112
|
+
"guide.hint": "Drag the right edge to resize",
|
|
102
113
|
"button.aria": "Chat width tier: {label} ({value}). Click to choose a tier.",
|
|
103
114
|
"button.title.summary": "Chat width tier: {label} ({value})",
|
|
104
115
|
"button.title.hint": "Click to choose: {list}"
|
|
105
116
|
};
|
|
106
117
|
//#endregion
|
|
107
118
|
|
|
108
|
-
/* Tier ids and label keys only —
|
|
109
|
-
*
|
|
110
|
-
*
|
|
119
|
+
/* Tier ids and label keys only — the picker resolves displayed values
|
|
120
|
+
* in tierValueText (fixed px for medium/wide/ultra, live px for
|
|
121
|
+
* standard/full, recorded px for custom). */
|
|
111
122
|
const TIERS = [
|
|
112
123
|
{ id: "standard", key: "tier.standard" },
|
|
113
124
|
{ id: "medium", key: "tier.medium" },
|
|
114
125
|
{ id: "wide", key: "tier.wide" },
|
|
115
126
|
{ id: "ultra", key: "tier.ultra" },
|
|
116
127
|
{ id: "full", key: "tier.full" },
|
|
117
|
-
/* Reached
|
|
128
|
+
/* Reached by dragging a native width handle, or by picking it in
|
|
129
|
+
* the menu and following the right-edge drag guide. */
|
|
118
130
|
{ id: "custom", key: "tier.custom" }
|
|
119
131
|
];
|
|
120
132
|
|
|
@@ -141,10 +153,15 @@ window.__ModuleLoader__.load({
|
|
|
141
153
|
"#dsh-width-menu[hidden]{display:none}",
|
|
142
154
|
"#dsh-width-menu button{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border:none;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:18px;text-align:left;cursor:pointer}",
|
|
143
155
|
"#dsh-width-menu button:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
144
|
-
"#dsh-width-menu button.disabled{opacity:.45;cursor:default;pointer-events:none}",
|
|
145
156
|
"#dsh-width-menu button.selected{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-state-business-primary)}",
|
|
146
157
|
"#dsh-width-menu .dshwm-value{color:var(--dsw-alias-label-tertiary);font-size:12px}",
|
|
147
|
-
"#dsh-width-menu button.selected .dshwm-value{color:var(--dsw-alias-state-business-primary)}"
|
|
158
|
+
"#dsh-width-menu button.selected .dshwm-value{color:var(--dsw-alias-state-business-primary)}",
|
|
159
|
+
"#dsh-width-guide{position:fixed;z-index:1001;display:flex;align-items:center;gap:8px;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-specific-menu);color:var(--dsw-alias-label-primary);font-size:12px;line-height:16px;box-shadow:0 8px 28px rgba(0,0,0,.24);pointer-events:none;white-space:nowrap;transform:translateY(-50%)}",
|
|
160
|
+
"#dsh-width-guide[hidden]{display:none}",
|
|
161
|
+
"#dsh-width-guide svg{flex:none;color:var(--dsw-alias-state-business-primary)}",
|
|
162
|
+
"#dsh-width-guide[data-mode=live]{font-size:13px;font-weight:600;font-variant-numeric:tabular-nums}",
|
|
163
|
+
"body[data-dsh-width-guide] [data-width-handle=right]{background:linear-gradient(to bottom,transparent calc(50% - 64px),color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent) calc(50% - 64px),color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent) calc(50% + 64px),transparent calc(50% + 64px));border-radius:3px}",
|
|
164
|
+
"body[data-dsh-width-guide] [data-width-handle=right]::after{opacity:1}"
|
|
148
165
|
].join("");
|
|
149
166
|
//#endregion
|
|
150
167
|
|
|
@@ -299,9 +316,10 @@ window.__ModuleLoader__.load({
|
|
|
299
316
|
function tierPx(tier, root) {
|
|
300
317
|
const cap = Math.max(CONTENT_MIN_PX, root.offsetWidth - CONTENT_EDGE_BUDGET_PX);
|
|
301
318
|
switch (tier) {
|
|
302
|
-
case "medium":
|
|
303
|
-
case "wide":
|
|
304
|
-
case "ultra":
|
|
319
|
+
case "medium":
|
|
320
|
+
case "wide":
|
|
321
|
+
case "ultra":
|
|
322
|
+
return Math.min(NOMINAL_WIDTH_PX[tier], cap);
|
|
305
323
|
case "full": return cap;
|
|
306
324
|
case "custom": {
|
|
307
325
|
const dragged = readStoredNumber(CUSTOM_KEY);
|
|
@@ -311,15 +329,23 @@ window.__ModuleLoader__.load({
|
|
|
311
329
|
}
|
|
312
330
|
}
|
|
313
331
|
|
|
314
|
-
/* The
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
332
|
+
/* The transcript width the app currently resolves for the live
|
|
333
|
+
* column (after its own clamp), used by the custom-drag guide.
|
|
334
|
+
* During a drag the app writes the clamped px to the target's
|
|
335
|
+
* inline `--dsh-chat-user-width`, so reading that is exact and
|
|
336
|
+
* avoids relying on how a browser serializes a computed custom
|
|
337
|
+
* property (which may stay as a var()/clamp() token stream). */
|
|
338
|
+
function liveWidthPx(root) {
|
|
339
|
+
const target = widthTarget(root);
|
|
340
|
+
const inline = Number.parseFloat(target.style.getPropertyValue("--dsh-chat-user-width"));
|
|
341
|
+
if (Number.isFinite(inline) && inline > 0) return Math.round(inline);
|
|
342
|
+
const raw = getComputedStyle(root).getPropertyValue("--dsh-chat-content-width");
|
|
343
|
+
const computed = Number.parseFloat(raw);
|
|
344
|
+
if (Number.isFinite(computed) && computed > 0) return Math.round(computed);
|
|
345
|
+
return Math.round(Math.max(
|
|
346
|
+
DEFAULT_MIN_PX,
|
|
347
|
+
Math.min(root.offsetWidth * DEFAULT_COLUMN_RATIO, DEFAULT_MAX_PX)
|
|
348
|
+
));
|
|
323
349
|
}
|
|
324
350
|
|
|
325
351
|
function applyContentWidth(tier) {
|
|
@@ -407,18 +433,38 @@ window.__ModuleLoader__.load({
|
|
|
407
433
|
return `<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false">${bars}</svg>`;
|
|
408
434
|
}
|
|
409
435
|
|
|
410
|
-
/*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
436
|
+
/* Value shown in the picker. Fixed tiers advertise their
|
|
437
|
+
* nominal px; standard and full are inherently relative to the
|
|
438
|
+
* current column, so they show the px the app resolves right
|
|
439
|
+
* now (full is the column's maximum, not a word). */
|
|
414
440
|
function tierValueText(tierId) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
441
|
+
switch (tierId) {
|
|
442
|
+
case "standard": {
|
|
443
|
+
const root = findWidthRoot();
|
|
444
|
+
if (root === null) return "—";
|
|
445
|
+
return `${Math.round(Math.max(
|
|
446
|
+
DEFAULT_MIN_PX,
|
|
447
|
+
Math.min(root.offsetWidth * DEFAULT_COLUMN_RATIO, DEFAULT_MAX_PX)
|
|
448
|
+
))}px`;
|
|
449
|
+
}
|
|
450
|
+
case "medium":
|
|
451
|
+
case "wide":
|
|
452
|
+
case "ultra":
|
|
453
|
+
return `${NOMINAL_WIDTH_PX[tierId]}px`;
|
|
454
|
+
case "full": {
|
|
455
|
+
const root = findWidthRoot();
|
|
456
|
+
if (root === null) return "—";
|
|
457
|
+
return `${Math.round(Math.max(
|
|
458
|
+
CONTENT_MIN_PX,
|
|
459
|
+
root.offsetWidth - CONTENT_EDGE_BUDGET_PX
|
|
460
|
+
))}px`;
|
|
461
|
+
}
|
|
462
|
+
case "custom": {
|
|
463
|
+
const dragged = readStoredNumber(CUSTOM_KEY);
|
|
464
|
+
return dragged === null ? t("value.custom.unset") : `${Math.round(dragged)}px`;
|
|
465
|
+
}
|
|
466
|
+
default: return "—";
|
|
419
467
|
}
|
|
420
|
-
const dragged = readStoredNumber(CUSTOM_KEY);
|
|
421
|
-
return dragged === null ? "—" : `${Math.round(dragged)}px`;
|
|
422
468
|
}
|
|
423
469
|
|
|
424
470
|
function syncButton() {
|
|
@@ -450,7 +496,14 @@ window.__ModuleLoader__.load({
|
|
|
450
496
|
item.innerHTML = `<span class="dshwm-label">${t(def.key)}</span><span class="dshwm-value">${tierValueText(def.id)}</span>`;
|
|
451
497
|
item.addEventListener("click", (e) => {
|
|
452
498
|
if (e && e.stopPropagation) e.stopPropagation();
|
|
453
|
-
|
|
499
|
+
/* Custom before the first drag has no width of its own:
|
|
500
|
+
* open the right-edge guide instead of applying a value. */
|
|
501
|
+
if (def.id === "custom" && readStoredNumber(CUSTOM_KEY) === null) {
|
|
502
|
+
beginCustomGuide();
|
|
503
|
+
hideMenu();
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
endGuide(false);
|
|
454
507
|
applyTier(def.id);
|
|
455
508
|
hideMenu();
|
|
456
509
|
});
|
|
@@ -476,10 +529,6 @@ window.__ModuleLoader__.load({
|
|
|
476
529
|
if (labelEl) setText(labelEl, t(tierOf(id).key));
|
|
477
530
|
const valueEl = items[i].querySelector(".dshwm-value");
|
|
478
531
|
if (valueEl) setText(valueEl, tierValueText(id));
|
|
479
|
-
/* custom is inert until the user has dragged once */
|
|
480
|
-
const disabled = id === "custom" && readStoredNumber(CUSTOM_KEY) === null;
|
|
481
|
-
items[i].classList.toggle("disabled", disabled);
|
|
482
|
-
items[i].setAttribute("aria-disabled", String(disabled));
|
|
483
532
|
}
|
|
484
533
|
}
|
|
485
534
|
|
|
@@ -501,15 +550,151 @@ window.__ModuleLoader__.load({
|
|
|
501
550
|
});
|
|
502
551
|
|
|
503
552
|
const onDocClick = (e) => {
|
|
504
|
-
if (menu.hidden)
|
|
505
|
-
|
|
553
|
+
if (!menu.hidden && e.target !== btn && !menu.contains(e.target)) hideMenu();
|
|
554
|
+
/* Clicking anywhere but the guide's handle cancels a
|
|
555
|
+
* pending custom guide and restores the previous tier. */
|
|
556
|
+
if (guideActive && !guideDragging && e.target !== btn && !menu.contains(e.target) && !isHandleEvent(e)) {
|
|
557
|
+
endGuide(true);
|
|
558
|
+
}
|
|
506
559
|
};
|
|
507
560
|
const onDocKey = (e) => {
|
|
508
|
-
if (e.key
|
|
561
|
+
if (e.key !== "Escape") return;
|
|
562
|
+
if (!menu.hidden) hideMenu();
|
|
563
|
+
if (guideActive) endGuide(true);
|
|
509
564
|
};
|
|
510
565
|
document.addEventListener("click", onDocClick);
|
|
511
566
|
document.addEventListener("keydown", onDocKey);
|
|
512
567
|
|
|
568
|
+
/* ---- custom-drag guide ----
|
|
569
|
+
* A first-time `custom` pick has no recorded width. Instead of
|
|
570
|
+
* silently keeping the current width, point at the app's
|
|
571
|
+
* right-edge width handle and show the live transcript width
|
|
572
|
+
* while the user drags. The guide never writes the width
|
|
573
|
+
* itself: the app's native drag does (and wins the cascade),
|
|
574
|
+
* and the plugin only records the settled value on release. */
|
|
575
|
+
const guide = document.createElement("div");
|
|
576
|
+
guide.id = "dsh-width-guide";
|
|
577
|
+
guide.setAttribute("aria-hidden", "true");
|
|
578
|
+
guide.hidden = true;
|
|
579
|
+
guide.innerHTML =
|
|
580
|
+
'<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" focusable="false">' +
|
|
581
|
+
'<path d="M6 3 2 8l4 5M10 3l4 5-4 5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
|
|
582
|
+
'<span class="dshwg-text"></span>';
|
|
583
|
+
const guideTextEl = guide.querySelector(".dshwg-text");
|
|
584
|
+
|
|
585
|
+
let guideActive = false;
|
|
586
|
+
let guideDragging = false;
|
|
587
|
+
/* true while the bubble came from picking custom in the menu (its
|
|
588
|
+
* cancel path restores the previous tier). Direct handle drags
|
|
589
|
+
* open the same bubble but never "cancel" a tier. */
|
|
590
|
+
let guideFromMenu = false;
|
|
591
|
+
let guideHideTimer = null;
|
|
592
|
+
let tierBeforeGuide = "standard";
|
|
593
|
+
|
|
594
|
+
/* The px the app currently resolves for the transcript, after
|
|
595
|
+
* its own clamp — what the guide's live readout shows. */
|
|
596
|
+
function guideValueText() {
|
|
597
|
+
const root = findWidthRoot();
|
|
598
|
+
const px = root === null ? null : liveWidthPx(root);
|
|
599
|
+
return px === null ? "—" : `${px}px`;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/* Keep the bubble beside the right handle (it moves with the
|
|
603
|
+
* dragged edge); fall back to the frame's right edge when the
|
|
604
|
+
* handle is not mounted yet (hero / settling phase). */
|
|
605
|
+
function positionGuide() {
|
|
606
|
+
if (!guideActive) return;
|
|
607
|
+
const handle = document.querySelector('[data-width-handle="right"]');
|
|
608
|
+
const viewport = window.innerWidth || 0;
|
|
609
|
+
if (handle === null || viewport === 0) {
|
|
610
|
+
guide.style.left = "auto";
|
|
611
|
+
guide.style.right = "24px";
|
|
612
|
+
guide.style.top = "50%";
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const rect = handle.getBoundingClientRect();
|
|
616
|
+
const gap = 10;
|
|
617
|
+
const width = guide.offsetWidth || 180;
|
|
618
|
+
guide.style.top = `${rect.top + rect.height / 2}px`;
|
|
619
|
+
guide.style.right = "auto";
|
|
620
|
+
if (viewport - rect.right >= width + gap) {
|
|
621
|
+
guide.style.left = `${rect.right + gap}px`;
|
|
622
|
+
} else {
|
|
623
|
+
guide.style.left = `${Math.max(8, rect.left - gap - width)}px`;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function setGuideText(mode, text) {
|
|
628
|
+
guide.dataset.mode = mode;
|
|
629
|
+
setText(guideTextEl, text);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function startGuide() {
|
|
633
|
+
guideFromMenu = true;
|
|
634
|
+
guideActive = true;
|
|
635
|
+
guideDragging = false;
|
|
636
|
+
guide.hidden = false;
|
|
637
|
+
body().setAttribute("data-dsh-width-guide", "custom");
|
|
638
|
+
setGuideText("hint", t("guide.hint"));
|
|
639
|
+
positionGuide();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/* Every handle press shows the live readout, not just the first
|
|
643
|
+
* guided drag — a repeat custom drag must keep the handle
|
|
644
|
+
* highlight and the width bubble. */
|
|
645
|
+
function startLiveGuide() {
|
|
646
|
+
window.clearTimeout(guideHideTimer);
|
|
647
|
+
guideActive = true;
|
|
648
|
+
guideDragging = true;
|
|
649
|
+
guide.hidden = false;
|
|
650
|
+
body().setAttribute("data-dsh-width-guide", "custom");
|
|
651
|
+
setGuideText("live", guideValueText());
|
|
652
|
+
positionGuide();
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function beginCustomGuide() {
|
|
656
|
+
/* Remember the tier to restore on cancel only on the first
|
|
657
|
+
* entry; re-picking custom while the guide is open must not
|
|
658
|
+
* make "custom" the restore target. */
|
|
659
|
+
if (!guideActive) {
|
|
660
|
+
tierBeforeGuide = currentTier();
|
|
661
|
+
applyTier("custom");
|
|
662
|
+
}
|
|
663
|
+
startGuide();
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/* Show the settled width briefly, then remove the guide. */
|
|
667
|
+
function finishGuide() {
|
|
668
|
+
if (!guideActive) return;
|
|
669
|
+
guideDragging = false;
|
|
670
|
+
setGuideText("live", guideValueText());
|
|
671
|
+
window.clearTimeout(guideHideTimer);
|
|
672
|
+
guideHideTimer = window.setTimeout(() => endGuide(false), 800);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/* `restoreTier` is for cancelling a menu-opened guide (click-away /
|
|
676
|
+
* Escape): if it never recorded a width, go back to the tier the
|
|
677
|
+
* user had before opening it. Drag-opened bubbles keep custom. */
|
|
678
|
+
function endGuide(restoreTier) {
|
|
679
|
+
if (!guideActive) return;
|
|
680
|
+
guideActive = false;
|
|
681
|
+
guideDragging = false;
|
|
682
|
+
window.clearTimeout(guideHideTimer);
|
|
683
|
+
guide.hidden = true;
|
|
684
|
+
guide.dataset.mode = "hint";
|
|
685
|
+
body().removeAttribute("data-dsh-width-guide");
|
|
686
|
+
const restore = restoreTier && guideFromMenu && readStoredNumber(CUSTOM_KEY) === null;
|
|
687
|
+
guideFromMenu = false;
|
|
688
|
+
if (restore) applyTier(tierBeforeGuide);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const onGuidePointerMove = () => {
|
|
692
|
+
if (!guideActive || !guideDragging) return;
|
|
693
|
+
setGuideText("live", guideValueText());
|
|
694
|
+
positionGuide();
|
|
695
|
+
};
|
|
696
|
+
document.addEventListener("pointermove", onGuidePointerMove, true);
|
|
697
|
+
|
|
513
698
|
/* ---- hand the width over to the app's native drag handles ----
|
|
514
699
|
* Pressing a `[data-width-handle]` means the user is taking the
|
|
515
700
|
* width over: switch to the `custom` tier so healing stops
|
|
@@ -521,7 +706,9 @@ window.__ModuleLoader__.load({
|
|
|
521
706
|
e.target instanceof Element && e.target.closest("[data-width-handle]") !== null;
|
|
522
707
|
|
|
523
708
|
const onHandlePointerDown = (e) => {
|
|
524
|
-
if (!isHandleEvent(e)
|
|
709
|
+
if (!isHandleEvent(e)) return;
|
|
710
|
+
startLiveGuide();
|
|
711
|
+
if (currentTier() === "custom") return;
|
|
525
712
|
body().setAttribute("data-dsh-width", "custom");
|
|
526
713
|
try {
|
|
527
714
|
window.localStorage.setItem(KEY, "custom");
|
|
@@ -536,16 +723,19 @@ window.__ModuleLoader__.load({
|
|
|
536
723
|
const onHandlePointerUp = (e) => {
|
|
537
724
|
if (!isHandleEvent(e)) return;
|
|
538
725
|
const root = findWidthRoot();
|
|
539
|
-
if (root
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
726
|
+
if (root !== null) {
|
|
727
|
+
const px = Number.parseFloat(
|
|
728
|
+
widthTarget(root).style.getPropertyValue("--dsh-chat-user-width")
|
|
729
|
+
);
|
|
730
|
+
if (Number.isFinite(px) && px > 0) {
|
|
731
|
+
try {
|
|
732
|
+
window.localStorage.setItem(CUSTOM_KEY, String(Math.round(px)));
|
|
733
|
+
} catch (err) {
|
|
734
|
+
/* ignore */
|
|
735
|
+
}
|
|
736
|
+
}
|
|
548
737
|
}
|
|
738
|
+
if (guideActive) finishGuide();
|
|
549
739
|
syncButton();
|
|
550
740
|
syncMenu();
|
|
551
741
|
};
|
|
@@ -564,6 +754,9 @@ window.__ModuleLoader__.load({
|
|
|
564
754
|
const renderTexts = () => {
|
|
565
755
|
syncButton();
|
|
566
756
|
syncMenu();
|
|
757
|
+
if (guideActive && !guideDragging && guide.dataset.mode === "hint") {
|
|
758
|
+
setText(guideTextEl, t("guide.hint"));
|
|
759
|
+
}
|
|
567
760
|
};
|
|
568
761
|
const unsubscribeLocale = ctx.locale.subscribe(renderTexts);
|
|
569
762
|
|
|
@@ -588,6 +781,7 @@ window.__ModuleLoader__.load({
|
|
|
588
781
|
}
|
|
589
782
|
body().appendChild(btn);
|
|
590
783
|
body().appendChild(menu);
|
|
784
|
+
body().appendChild(guide);
|
|
591
785
|
syncButton();
|
|
592
786
|
const saved = getSavedTier();
|
|
593
787
|
if (saved !== "standard") applyTier(saved);
|
|
@@ -648,6 +842,9 @@ window.__ModuleLoader__.load({
|
|
|
648
842
|
}
|
|
649
843
|
|
|
650
844
|
function heal(fromObserver) {
|
|
845
|
+
/* The right handle only mounts in the active phase; keep the
|
|
846
|
+
* guide pinned to it once the chat tree settles. */
|
|
847
|
+
if (guideActive) positionGuide();
|
|
651
848
|
const tier = currentTier();
|
|
652
849
|
if (tier === "standard") return;
|
|
653
850
|
const prev = widthRoot;
|
|
@@ -702,10 +899,9 @@ window.__ModuleLoader__.load({
|
|
|
702
899
|
fallbackTimer = setInterval(() => heal(false), FALLBACK_HEAL_MS);
|
|
703
900
|
}
|
|
704
901
|
|
|
705
|
-
/* ---- keep the
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
* The menu is also re-synced whenever it opens. */
|
|
902
|
+
/* ---- keep the picker and the guide aligned with the live
|
|
903
|
+
* column. Standard/full re-resolve on resize, and the guide
|
|
904
|
+
* must stay beside the handle when the frame resizes. ---- */
|
|
709
905
|
let resizeFrame = null;
|
|
710
906
|
|
|
711
907
|
const onWindowResize = () => {
|
|
@@ -714,6 +910,7 @@ window.__ModuleLoader__.load({
|
|
|
714
910
|
resizeFrame = null;
|
|
715
911
|
syncButton();
|
|
716
912
|
syncMenu();
|
|
913
|
+
positionGuide();
|
|
717
914
|
});
|
|
718
915
|
};
|
|
719
916
|
window.addEventListener("resize", onWindowResize);
|
|
@@ -725,6 +922,7 @@ window.__ModuleLoader__.load({
|
|
|
725
922
|
clearInterval(fallbackTimer);
|
|
726
923
|
clearTimeout(timeout);
|
|
727
924
|
clearTimeout(rootRetryTimer);
|
|
925
|
+
clearTimeout(guideHideTimer);
|
|
728
926
|
if (healFrame !== null) cancelFrame(healFrame);
|
|
729
927
|
if (resizeFrame !== null) cancelFrame(resizeFrame);
|
|
730
928
|
window.removeEventListener("resize", onWindowResize);
|
|
@@ -733,8 +931,10 @@ window.__ModuleLoader__.load({
|
|
|
733
931
|
document.removeEventListener("keydown", onDocKey);
|
|
734
932
|
document.removeEventListener("pointerdown", onHandlePointerDown, true);
|
|
735
933
|
document.removeEventListener("pointerup", onHandlePointerUp, true);
|
|
934
|
+
document.removeEventListener("pointermove", onGuidePointerMove, true);
|
|
736
935
|
if (btn.parentNode) btn.parentNode.removeChild(btn);
|
|
737
936
|
if (menu.parentNode) menu.parentNode.removeChild(menu);
|
|
937
|
+
if (guide.parentNode) guide.parentNode.removeChild(guide);
|
|
738
938
|
if (styleTag.parentNode) styleTag.parentNode.removeChild(styleTag);
|
|
739
939
|
/* Hand the width back to the app default; the app's own
|
|
740
940
|
* preference (which this plugin mirrors) stays so the last
|
|
@@ -744,6 +944,7 @@ window.__ModuleLoader__.load({
|
|
|
744
944
|
widthTarget(widthRoot).style.removeProperty("--dsh-chat-user-width");
|
|
745
945
|
}
|
|
746
946
|
body().removeAttribute("data-dsh-width");
|
|
947
|
+
body().removeAttribute("data-dsh-width-guide");
|
|
747
948
|
};
|
|
748
949
|
}, "dsh-width-tiers: ui");
|
|
749
950
|
}
|
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 / custom) with a floating picker button. Drives the app's native width axis on DSH 0.1.7+ (
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "DSH Web client plugin: chat content width tiers (standard / medium / wide / ultra / full / custom) with a floating picker button. Drives the app's native width axis on DSH 0.1.7+ (0.2.1-rc.1 width axis checked), so the built-in drag handles keep working. UI copy follows the app language (zh / en).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "aaronlei",
|
|
7
7
|
"publishConfig": {
|