dsh-milestone 0.6.1 → 0.6.3
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 +219 -134
- package/assets/demo.svg +39 -0
- package/assets/logo.svg +22 -0
- package/lib/client.js +1916 -660
- package/package.json +3 -2
package/lib/client.js
CHANGED
|
@@ -19,8 +19,105 @@ window.__ModuleLoader__.load({
|
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
//#endregion
|
|
22
|
+
//#region src/client/accent-utils.ts
|
|
23
|
+
/** True for a canonical 6-digit hex color (`#4d7cfd`, `#4D7CFD`). */
|
|
24
|
+
function isHexColor(value) {
|
|
25
|
+
return /^#[0-9a-fA-F]{6}$/.test(value);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a hex color into channels.
|
|
29
|
+
* @param hex - `#rrggbb` (case-insensitive).
|
|
30
|
+
* @returns the RGB channels, or null when `hex` is not a canonical hex color.
|
|
31
|
+
*/
|
|
32
|
+
function hexToRgb(hex) {
|
|
33
|
+
if (!isHexColor(hex)) return null;
|
|
34
|
+
const n = parseInt(hex.slice(1), 16);
|
|
35
|
+
return {
|
|
36
|
+
r: n >> 16 & 255,
|
|
37
|
+
g: n >> 8 & 255,
|
|
38
|
+
b: n & 255
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Format an alpha to the short decimal form the rail's inline styles use (0.55, 0.2). */
|
|
42
|
+
function formatAlpha(alpha) {
|
|
43
|
+
return Math.round(Math.max(0, Math.min(1, alpha)) * 100) / 100;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Color with alpha as a CSS `rgba(r, g, b, a)` string; alpha clamps to [0, 1].
|
|
47
|
+
* @returns the rgba() string, or null when `hex` is invalid.
|
|
48
|
+
*/
|
|
49
|
+
function rgbaString(hex, alpha) {
|
|
50
|
+
const rgb = hexToRgb(hex);
|
|
51
|
+
if (rgb === null) return null;
|
|
52
|
+
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${formatAlpha(alpha)})`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Linear mix of two hex colors in RGB space.
|
|
56
|
+
* @param a - start color (t = 0).
|
|
57
|
+
* @param b - end color (t = 1).
|
|
58
|
+
* @param t - mix factor, clamped to [0, 1].
|
|
59
|
+
* @returns the mixed `#rrggbb`, or null when either input is invalid.
|
|
60
|
+
*/
|
|
61
|
+
function mixHex(a, b, t) {
|
|
62
|
+
const ca = hexToRgb(a);
|
|
63
|
+
const cb = hexToRgb(b);
|
|
64
|
+
if (ca === null || cb === null) return null;
|
|
65
|
+
const k = Math.max(0, Math.min(1, t));
|
|
66
|
+
const channel = (from, to) => Math.round(from + (to - from) * k).toString(16).padStart(2, "0");
|
|
67
|
+
return `#${channel(ca.r, cb.r)}${channel(ca.g, cb.g)}${channel(ca.b, cb.b)}`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Lighten a hex color toward white — the rail's "accent soft" text shade
|
|
71
|
+
* (t = 0 keeps the color, t = 1 is white).
|
|
72
|
+
*/
|
|
73
|
+
function lighten(hex, t) {
|
|
74
|
+
return mixHex(hex, "#ffffff", t);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Convert a hex color to HSL. Double-checked against the standard RGB→HSL
|
|
78
|
+
* formulas; saturation/lightness are percentages, hue is degrees.
|
|
79
|
+
* @returns the HSL channels, or null when `hex` is invalid.
|
|
80
|
+
*/
|
|
81
|
+
function hexToHsl(hex) {
|
|
82
|
+
const rgb = hexToRgb(hex);
|
|
83
|
+
if (rgb === null) return null;
|
|
84
|
+
const r = rgb.r / 255;
|
|
85
|
+
const g = rgb.g / 255;
|
|
86
|
+
const b = rgb.b / 255;
|
|
87
|
+
const max = Math.max(r, g, b);
|
|
88
|
+
const min = Math.min(r, g, b);
|
|
89
|
+
const delta = max - min;
|
|
90
|
+
let h = 0;
|
|
91
|
+
if (delta !== 0) if (max === r) h = 60 * ((g - b) / delta % 6);
|
|
92
|
+
else if (max === g) h = 60 * ((b - r) / delta + 2);
|
|
93
|
+
else h = 60 * ((r - g) / delta + 4);
|
|
94
|
+
if (h < 0) h += 360;
|
|
95
|
+
const l = (max + min) / 2;
|
|
96
|
+
const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
|
|
97
|
+
return {
|
|
98
|
+
h,
|
|
99
|
+
s: s * 100,
|
|
100
|
+
l: l * 100
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
22
104
|
//#region src/client/badge-logic.ts
|
|
23
105
|
/**
|
|
106
|
+
* Pure turn-health badge derivation + styling for the milestone rail: given
|
|
107
|
+
* the harness snapshot signals, decide which (if any) colored glow a mark's
|
|
108
|
+
* dot should wear, plus the style tokens for that badge.
|
|
109
|
+
*
|
|
110
|
+
* Rendering contract (M-design change): a badge is NO LONGER a hard 2px ring
|
|
111
|
+
* (border) — it is a concentric "soft glow" made of three layered box-shadows
|
|
112
|
+
* (a crisp inner ring at 55% alpha, then two blurred blooms), and the
|
|
113
|
+
* running/awaiting pulse breathes opacity + shadow intensity instead of
|
|
114
|
+
* expanding a ring. The `data-badge` value and the semantic color of every
|
|
115
|
+
* kind are unchanged.
|
|
116
|
+
*
|
|
117
|
+
* All functions are side-effect free (no React, no DOM) so the rail component
|
|
118
|
+
* can consume them directly and tests can exercise them in isolation.
|
|
119
|
+
*/
|
|
120
|
+
/**
|
|
24
121
|
* Derive the badge for one mark.
|
|
25
122
|
*
|
|
26
123
|
* Precedence: error > max-tokens > retry > running > awaiting. Node-derived
|
|
@@ -65,13 +162,41 @@ window.__ModuleLoader__.load({
|
|
|
65
162
|
pulse: true
|
|
66
163
|
}
|
|
67
164
|
};
|
|
165
|
+
/** Build the layered soft-glow shadow for a badge color. */
|
|
166
|
+
function glowShadow(color) {
|
|
167
|
+
const layer = (alpha) => rgbaString(color, alpha) ?? "currentColor";
|
|
168
|
+
return [
|
|
169
|
+
`0 0 0 2px ${layer(.55)}`,
|
|
170
|
+
`0 0 8px 2px ${layer(.45)}`,
|
|
171
|
+
`0 0 16px 5px ${layer(.2)}`
|
|
172
|
+
].join(", ");
|
|
173
|
+
}
|
|
68
174
|
/**
|
|
69
175
|
* Style tokens for a badge kind.
|
|
70
176
|
* @param badge - the derived badge kind.
|
|
71
|
-
* @returns the
|
|
177
|
+
* @returns the glow color, whether the dot should pulse (breathing glow), and
|
|
178
|
+
* the static multi-layer box-shadow string for the badge span.
|
|
72
179
|
*/
|
|
73
180
|
function badgeRingStyle(badge) {
|
|
74
|
-
|
|
181
|
+
const base = RING_STYLES[badge];
|
|
182
|
+
return {
|
|
183
|
+
...base,
|
|
184
|
+
shadow: glowShadow(base.color)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Breathing-glow keyframes for one badge color. The rail injects this (via an
|
|
189
|
+
* inline <style>) only while a pulsing badge is on screen — the color is baked
|
|
190
|
+
* into the alphas, so the animation needs no runtime var lookups. Both stops
|
|
191
|
+
* keep the SAME three-layer shape (only alpha/blur breathe), so the glow never
|
|
192
|
+
* looks like the old expanding ring.
|
|
193
|
+
*/
|
|
194
|
+
function badgePulseCss(color) {
|
|
195
|
+
const layer = (alpha) => rgbaString(color, alpha) ?? "currentColor";
|
|
196
|
+
return `@keyframes milestone-badge-pulse {
|
|
197
|
+
0%, 100% { opacity: 0.95; box-shadow: ${`0 0 0 2px ${layer(.55)}, 0 0 8px 2px ${layer(.45)}, 0 0 16px 5px ${layer(.2)}`}; }
|
|
198
|
+
50% { opacity: 0.45; box-shadow: ${`0 0 0 2px ${layer(.35)}, 0 0 4px 1px ${layer(.25)}, 0 0 9px 3px ${layer(.12)}`}; }
|
|
199
|
+
}`;
|
|
75
200
|
}
|
|
76
201
|
//#endregion
|
|
77
202
|
//#region src/client/bookmark-logic.ts
|
|
@@ -349,6 +474,13 @@ window.__ModuleLoader__.load({
|
|
|
349
474
|
//#endregion
|
|
350
475
|
//#region src/client/rail-logic.ts
|
|
351
476
|
/**
|
|
477
|
+
* Pure rail logic for the milestone rail: full-text search matching, current-
|
|
478
|
+
* position highlight, match-cycle navigation, mark visual state, and dot color.
|
|
479
|
+
*
|
|
480
|
+
* All functions are side-effect free (no React, no DOM) so the rail component
|
|
481
|
+
* can consume them directly and tests can exercise them in isolation.
|
|
482
|
+
*/
|
|
483
|
+
/**
|
|
352
484
|
* Extract the FULL plain text of a ContentBlock[] payload: the `text` of every
|
|
353
485
|
* `{ type: 'text', text: string }` block, joined with a single space and
|
|
354
486
|
* trimmed. Unlike the rail's hover preview this is NOT truncated — callers use
|
|
@@ -427,14 +559,366 @@ window.__ModuleLoader__.load({
|
|
|
427
559
|
if (opts.hasQuery) return "dimmed";
|
|
428
560
|
return "normal";
|
|
429
561
|
}
|
|
562
|
+
/** The default accent (the classic milestone blue) — dotColor's fallback. */
|
|
563
|
+
const DEFAULT_DOT_ACCENT = "#4d7cfd";
|
|
430
564
|
/**
|
|
431
|
-
*
|
|
432
|
-
* (
|
|
565
|
+
* Accent-driven gradient dot color: hue/saturation come from the user's
|
|
566
|
+
* accent (settings 强调色), lightness walks 72% → 45% (newest/highest index
|
|
567
|
+
* deepest, oldest lightest — the original gradient shape). Invalid accents
|
|
568
|
+
* degrade to the default blue.
|
|
433
569
|
* @param index - dot position in the rail (0 = oldest).
|
|
434
570
|
* @param total - number of dots.
|
|
571
|
+
* @param accent - the accent hex (`#rrggbb`); defaults to the classic blue.
|
|
572
|
+
*/
|
|
573
|
+
function dotColor(index, total, accent = DEFAULT_DOT_ACCENT) {
|
|
574
|
+
const hsl = hexToHsl(accent) ?? hexToHsl("#4d7cfd");
|
|
575
|
+
const lightness = 72 - (total <= 1 ? 0 : index / (total - 1)) * 27;
|
|
576
|
+
return `hsl(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${lightness}%)`;
|
|
577
|
+
}
|
|
578
|
+
//#endregion
|
|
579
|
+
//#region src/client/locales.ts
|
|
580
|
+
/**
|
|
581
|
+
* UI strings for the milestone rail, keyed flat (single-language-per-key,
|
|
582
|
+
* no nesting) so the later i18n threading stays a mechanical
|
|
583
|
+
* `value.replace('{name}', n)` substitution.
|
|
584
|
+
*
|
|
585
|
+
* `zh` is the source of truth and the key registry: it byte-matches the
|
|
586
|
+
* current hardcoded output of MilestoneRail / MilestoneRailTooltip /
|
|
587
|
+
* MilestoneRailSearch exactly (each `{n}`/`{m}`/`{name}` slot stands in for
|
|
588
|
+
* the interpolated number or label), so swapping in these templates is
|
|
589
|
+
* behavior-preserving. `en` is typed `Record<MilestoneKey, string>` so a
|
|
590
|
+
* missing English translation is a compile error, not a runtime miss.
|
|
591
|
+
*/
|
|
592
|
+
const zh = {
|
|
593
|
+
/** aria-label on each dot: `跳转到第 ${i + 1} 条消息`. */
|
|
594
|
+
"jump.to": "跳转到第 {n} 条消息",
|
|
595
|
+
/** Load-older coverage hint: `已显示 {marks.length} 条 · 还有更早`. */
|
|
596
|
+
"window.hint": "已显示 {n} 条 · 还有更早",
|
|
597
|
+
/** Hover turn badge: `第 ${mark.turn} 轮`. */
|
|
598
|
+
"turn.label": "第 {n} 轮",
|
|
599
|
+
/** Hover position: `第 {hover.index + 1} / {hover.total} 条`. */
|
|
600
|
+
"pos.of": "第 {n} / {m} 条",
|
|
601
|
+
/** Search input placeholder. */
|
|
602
|
+
"search.placeholder": "搜索消息内容",
|
|
603
|
+
/** aria-label on the search toggle button and the search input. */
|
|
604
|
+
"search.label": "搜索消息",
|
|
605
|
+
/** aria-label on the bookmarks-only filter toggle. */
|
|
606
|
+
"bookmark.filter": "只看收藏",
|
|
607
|
+
/** aria-label + title on the focus-mode toggle when focus is OFF (arm it). */
|
|
608
|
+
"focus.on": "聚焦模式",
|
|
609
|
+
/** aria-label + title on the focus-mode toggle when focus is ON (disarm it). */
|
|
610
|
+
"focus.off": "退出聚焦",
|
|
611
|
+
/** aria-label on the hover tooltip star toggle. */
|
|
612
|
+
"bookmark.star": "收藏此消息",
|
|
613
|
+
/** aria-label on the search clear button. */
|
|
614
|
+
"search.clear": "清空搜索",
|
|
615
|
+
/** title + aria-label on the load-older `···` button. */
|
|
616
|
+
"load.older": "加载更早消息",
|
|
617
|
+
/** aria-label on the rail root. */
|
|
618
|
+
"rail.label": "会话里程碑",
|
|
619
|
+
/** aria-label on the dot list. */
|
|
620
|
+
"rail.list": "会话里程碑列表",
|
|
621
|
+
/** Hover preview fallback for empty message text. */
|
|
622
|
+
"no.text": "(无文本)",
|
|
623
|
+
/** Relative time: `< 60s`. */
|
|
624
|
+
"time.justNow": "刚刚",
|
|
625
|
+
/** Relative time: `< 1h`. */
|
|
626
|
+
"time.minutes": "{n} 分钟前",
|
|
627
|
+
/** Relative time: `< 1d`. */
|
|
628
|
+
"time.hours": "{n} 小时前",
|
|
629
|
+
/** Relative time: `>= 1d`. */
|
|
630
|
+
"time.days": "{n} 天前",
|
|
631
|
+
/** Hover duration: `用时 {durationLabel}`. */
|
|
632
|
+
"duration.label": "用时 {name}",
|
|
633
|
+
/** Hover TTFT: `首字 {ttftLabel}`. */
|
|
634
|
+
"ttft.label": "首字 {name}",
|
|
635
|
+
/** TurnEndReason `completed`. */
|
|
636
|
+
"reason.completed": "已完成",
|
|
637
|
+
/** TurnEndReason `aborted`. */
|
|
638
|
+
"reason.aborted": "已中止",
|
|
639
|
+
/** TurnEndReason `error`. */
|
|
640
|
+
"reason.error": "出错",
|
|
641
|
+
/** TurnEndReason `max-tokens`. */
|
|
642
|
+
"reason.maxTokens": "达到上限",
|
|
643
|
+
/** TurnEndReason `interrupted`. */
|
|
644
|
+
"reason.interrupted": "已中断",
|
|
645
|
+
/** TurnEndReason `blocked`. */
|
|
646
|
+
"reason.blocked": "已阻塞",
|
|
647
|
+
/** Copy-message tooltip action. */
|
|
648
|
+
"copy.message": "复制消息",
|
|
649
|
+
/** Fork-from-here tooltip action. */
|
|
650
|
+
"fork.here": "从此处 fork",
|
|
651
|
+
/** Collapse-turn tooltip action. */
|
|
652
|
+
"collapse.turn": "折叠此轮",
|
|
653
|
+
/** Expand-turn tooltip action. */
|
|
654
|
+
"expand.turn": "展开此轮",
|
|
655
|
+
/** aria-label + title on the milestone-list toggle when the panel is CLOSED. */
|
|
656
|
+
"list.open": "打开列表",
|
|
657
|
+
/** aria-label + title on the milestone-list toggle when the panel is OPEN. */
|
|
658
|
+
"list.close": "收起列表",
|
|
659
|
+
/** Header title of the all-prompts list panel. */
|
|
660
|
+
"list.label": "全部提问",
|
|
661
|
+
/** Header title + input placeholder of the cross-session search panel. */
|
|
662
|
+
"search.cross": "跨会话搜索",
|
|
663
|
+
/** aria-label + title on the cross-session search toggle when the panel is CLOSED. */
|
|
664
|
+
"search.cross.open": "打开跨会话搜索",
|
|
665
|
+
/** aria-label + title on the cross-session search toggle when the panel is OPEN. */
|
|
666
|
+
"search.cross.close": "收起跨会话搜索",
|
|
667
|
+
/** Cross-session result row title fallback for sessions with no display title. */
|
|
668
|
+
"search.untitled": "(无标题)",
|
|
669
|
+
/** Cross-session search failure notice. */
|
|
670
|
+
"search.error": "搜索失败,请重试",
|
|
671
|
+
/** Cross-session search footer hint when the harness capped the result list. */
|
|
672
|
+
"search.more": "结果已截断,请细化关键词",
|
|
673
|
+
/** aria-label on the toolbar expand arrow while the toolbar is COLLAPSED (expand it). */
|
|
674
|
+
"toolbar.expand": "展开工具栏",
|
|
675
|
+
/** aria-label on the toolbar expand arrow while the toolbar is EXPANDED (collapse it). */
|
|
676
|
+
"toolbar.collapse": "收起工具栏",
|
|
677
|
+
/** aria-label on the toolbar settings gear while the settings menu is CLOSED. */
|
|
678
|
+
"toolbar.settings.open": "打开设置",
|
|
679
|
+
/** aria-label on the toolbar settings gear while the settings menu is OPEN. */
|
|
680
|
+
"toolbar.settings.close": "关闭设置",
|
|
681
|
+
/** Header title of the toolbar settings menu. */
|
|
682
|
+
"settings.title": "设置",
|
|
683
|
+
/** Per-feature toggle label inside the settings menu: keep visible while collapsed. */
|
|
684
|
+
"settings.pin": "在折叠外显示",
|
|
685
|
+
/** Settings: feature-section hint explaining what the pin switch does. */
|
|
686
|
+
"settings.pin.hint": "开启后,工具栏折叠时该功能仍显示在箭头旁",
|
|
687
|
+
/** Settings action that clears every pinned feature. */
|
|
688
|
+
"settings.reset": "恢复默认",
|
|
689
|
+
/** Settings footer heading above the project links. */
|
|
690
|
+
"settings.support": "支持我们",
|
|
691
|
+
/** Settings footer link: the GitHub repository. */
|
|
692
|
+
"settings.repo": "GitHub 仓库",
|
|
693
|
+
/** Settings footer link: star the repository. */
|
|
694
|
+
"settings.star": "欢迎 Star ★",
|
|
695
|
+
/** Settings footer link: file an issue. */
|
|
696
|
+
"settings.issues": "提交 Issue",
|
|
697
|
+
/** Settings footer link: the npm install channel. */
|
|
698
|
+
"settings.npm": "npm 安装渠道",
|
|
699
|
+
/** Settings: feature-row name of the settings key itself (registry row). */
|
|
700
|
+
"settings.label": "设置",
|
|
701
|
+
/** aria-label on the settings modal close button. */
|
|
702
|
+
"settings.close": "关闭",
|
|
703
|
+
/** Settings modal: section heading for the feature pin rows. */
|
|
704
|
+
"settings.section.features": "功能与快捷区",
|
|
705
|
+
/** Settings modal: section heading for the personalization controls. */
|
|
706
|
+
"settings.section.personal": "个性化",
|
|
707
|
+
/** Settings modal: section heading for the focus-mode controls (0.6.3). */
|
|
708
|
+
"settings.section.focus": "聚焦",
|
|
709
|
+
/** Settings: personalization-section hint shown inside the expanded block. */
|
|
710
|
+
"settings.personal.hint": "圆点、强调色与位置,即调即存",
|
|
711
|
+
/** Settings: aria-label on the personalization block toggle while COLLAPSED. */
|
|
712
|
+
"settings.personal.expand": "展开个性化设置",
|
|
713
|
+
/** Settings: aria-label on the personalization block toggle while EXPANDED. */
|
|
714
|
+
"settings.personal.collapse": "收起个性化设置",
|
|
715
|
+
/** Settings: live value summary on the collapsed personalization header. */
|
|
716
|
+
"settings.personal.summary": "{accent} · 图标 {icon}px · {side}",
|
|
717
|
+
/** Settings: hover description — in-rail search. */
|
|
718
|
+
"settings.desc.search": "按完整消息内容过滤并跳转到对应消息",
|
|
719
|
+
/** Settings: hover description — all-prompts list. */
|
|
720
|
+
"settings.desc.list": "本会话全部提问一览",
|
|
721
|
+
/** Settings: hover description — cross-session search. */
|
|
722
|
+
"settings.desc.sessionSearch": "跨会话搜索所有会话",
|
|
723
|
+
/** Settings: hover description — bookmarks filter. */
|
|
724
|
+
"settings.desc.bookmarks": "只显示已收藏的消息",
|
|
725
|
+
/** Settings: hover description — focus mode. */
|
|
726
|
+
"settings.desc.focus": "淡化 AI 思考块,阅读更清爽",
|
|
727
|
+
/** Settings: hover description — update check. */
|
|
728
|
+
"settings.desc.updateCheck": "检查 npm 是否有新版本",
|
|
729
|
+
/** Settings: hover description — the settings key itself. */
|
|
730
|
+
"settings.desc.settings": "自定义工具栏与外观",
|
|
731
|
+
/** Settings personalization: accent color row label. */
|
|
732
|
+
"settings.accent": "强调色",
|
|
733
|
+
/** Settings personalization: custom color swatch label. */
|
|
734
|
+
"settings.custom": "自定义",
|
|
735
|
+
/** Settings personalization: icon/dot size slider label. */
|
|
736
|
+
"settings.iconSize": "图标 / 圆点大小",
|
|
737
|
+
/** Settings personalization: edge-distance slider label. */
|
|
738
|
+
"settings.inset": "距侧边距离",
|
|
739
|
+
/** Settings personalization: rail side row label. */
|
|
740
|
+
"settings.side": "位置",
|
|
741
|
+
/** Settings personalization: side radio — hug the left edge. */
|
|
742
|
+
"settings.side.left": "左侧",
|
|
743
|
+
/** Settings personalization: side radio — hug the right edge. */
|
|
744
|
+
"settings.side.right": "右侧",
|
|
745
|
+
/** Settings: focus block — hint shown inside the expanded block. */
|
|
746
|
+
"settings.focus.hint": "这些选项自由组合成你的「聚焦搭配」;总开关仍是工具栏的眼睛按钮",
|
|
747
|
+
/** Settings: aria-label on the focus block toggle while COLLAPSED. */
|
|
748
|
+
"settings.focus.expand": "展开聚焦设置",
|
|
749
|
+
/** Settings: aria-label on the focus block toggle while EXPANDED. */
|
|
750
|
+
"settings.focus.collapse": "收起聚焦设置",
|
|
751
|
+
/** Settings: focus option — dim the think reasoning disclosures. */
|
|
752
|
+
"settings.focus.dimThink": "淡化 think 推理区",
|
|
753
|
+
/** Settings: focus option — dim the tool-call cards. */
|
|
754
|
+
"settings.focus.dimTools": "淡化工具调用卡片",
|
|
755
|
+
/** Settings: focus option — compress think disclosures to a hover strip. */
|
|
756
|
+
"settings.focus.collapseThink": "折叠 think",
|
|
757
|
+
/** Settings: focus option — dim strength slider label. */
|
|
758
|
+
"settings.focus.opacity": "淡化强度",
|
|
759
|
+
/** Settings: live value summary on the collapsed focus header. */
|
|
760
|
+
"settings.focus.summary": "{opts} · 强度 {opacity}%",
|
|
761
|
+
/** Settings: focus summary — short label for the think-dim option. */
|
|
762
|
+
"settings.focus.summary.think": "think 淡化",
|
|
763
|
+
/** Settings: focus summary — short label for the tool-dim option. */
|
|
764
|
+
"settings.focus.summary.tools": "工具淡化",
|
|
765
|
+
/** Settings: focus summary — short label for the think-collapse option. */
|
|
766
|
+
"settings.focus.summary.collapse": "折叠 think",
|
|
767
|
+
/** Settings: focus summary — placeholder when every option is off. */
|
|
768
|
+
"settings.focus.summary.none": "未启用",
|
|
769
|
+
/** B4 update-check: toolbar button label + title/aria-label. */
|
|
770
|
+
"update.check": "检查更新",
|
|
771
|
+
/** B4 update-check: popover title. */
|
|
772
|
+
"update.title": "更新检测",
|
|
773
|
+
/** B4 update-check: installed-version row label. */
|
|
774
|
+
"update.current": "当前版本",
|
|
775
|
+
/** B4 update-check: newest-published-version row label. */
|
|
776
|
+
"update.latest": "最新版本",
|
|
777
|
+
/** B4 update-check: conclusion when the installed version is current. */
|
|
778
|
+
"update.upToDate": "已是最新版本",
|
|
779
|
+
/** B4 update-check: conclusion when a newer version exists. */
|
|
780
|
+
"update.available": "发现新版本",
|
|
781
|
+
/** B4 update-check: link text for the npm upgrade channel. */
|
|
782
|
+
"update.goNpm": "去 npm 升级",
|
|
783
|
+
/** B4 update-check: supported-host-lines metadata row label. */
|
|
784
|
+
"update.hostLines": "已适配官方版本线",
|
|
785
|
+
/** B4 update-check: in-flight state of the manual check button. */
|
|
786
|
+
"update.checking": "检查中…",
|
|
787
|
+
/** B4 update-check: failed state heading. */
|
|
788
|
+
"update.failed": "检查失败",
|
|
789
|
+
/** B4 update-check: retry action inside the failed state. */
|
|
790
|
+
"update.retry": "重试",
|
|
791
|
+
/** Settings modal section title: language. */
|
|
792
|
+
"settings.language": "语言",
|
|
793
|
+
/** Language option: follow the harness UI language. */
|
|
794
|
+
"settings.lang.system": "跟随系统",
|
|
795
|
+
/** Language option: force Chinese copy. */
|
|
796
|
+
"settings.lang.zh": "中文",
|
|
797
|
+
/** Language option: force English copy. */
|
|
798
|
+
"settings.lang.en": "English"
|
|
799
|
+
};
|
|
800
|
+
const en = {
|
|
801
|
+
"jump.to": "Jump to message {n}",
|
|
802
|
+
"window.hint": "Showing {n} messages · more below",
|
|
803
|
+
"turn.label": "Turn {n}",
|
|
804
|
+
"pos.of": "Message {n} of {m}",
|
|
805
|
+
"search.placeholder": "Search message content",
|
|
806
|
+
"search.label": "Search messages",
|
|
807
|
+
"bookmark.filter": "Bookmarks only",
|
|
808
|
+
"focus.on": "Focus mode",
|
|
809
|
+
"focus.off": "Exit focus",
|
|
810
|
+
"bookmark.star": "Bookmark this message",
|
|
811
|
+
"search.clear": "Clear search",
|
|
812
|
+
"load.older": "Load older messages",
|
|
813
|
+
"rail.label": "Session milestones",
|
|
814
|
+
"rail.list": "Session milestone list",
|
|
815
|
+
"no.text": "(no text)",
|
|
816
|
+
"time.justNow": "Just now",
|
|
817
|
+
"time.minutes": "{n} minutes ago",
|
|
818
|
+
"time.hours": "{n} hours ago",
|
|
819
|
+
"time.days": "{n} days ago",
|
|
820
|
+
"duration.label": "Duration {name}",
|
|
821
|
+
"ttft.label": "First token {name}",
|
|
822
|
+
"reason.completed": "Completed",
|
|
823
|
+
"reason.aborted": "Aborted",
|
|
824
|
+
"reason.error": "Error",
|
|
825
|
+
"reason.maxTokens": "Max tokens reached",
|
|
826
|
+
"reason.interrupted": "Interrupted",
|
|
827
|
+
"reason.blocked": "Blocked",
|
|
828
|
+
"copy.message": "Copy message",
|
|
829
|
+
"fork.here": "Fork from here",
|
|
830
|
+
"collapse.turn": "Collapse turn",
|
|
831
|
+
"expand.turn": "Expand turn",
|
|
832
|
+
"list.open": "Open list",
|
|
833
|
+
"list.close": "Close list",
|
|
834
|
+
"list.label": "All prompts",
|
|
835
|
+
"search.cross": "Cross-session search",
|
|
836
|
+
"search.cross.open": "Open cross-session search",
|
|
837
|
+
"search.cross.close": "Close cross-session search",
|
|
838
|
+
"search.untitled": "(untitled)",
|
|
839
|
+
"search.error": "Search failed, retry",
|
|
840
|
+
"search.more": "Results truncated — refine your query",
|
|
841
|
+
"toolbar.expand": "Expand toolbar",
|
|
842
|
+
"toolbar.collapse": "Collapse toolbar",
|
|
843
|
+
"toolbar.settings.open": "Open settings",
|
|
844
|
+
"toolbar.settings.close": "Close settings",
|
|
845
|
+
"settings.title": "Settings",
|
|
846
|
+
"settings.pin": "Show outside collapse",
|
|
847
|
+
"settings.pin.hint": "When on, the feature stays beside the arrow while the toolbar is folded",
|
|
848
|
+
"settings.reset": "Restore defaults",
|
|
849
|
+
"settings.support": "Support us",
|
|
850
|
+
"settings.repo": "GitHub repo",
|
|
851
|
+
"settings.star": "Give us a Star ★",
|
|
852
|
+
"settings.issues": "Report an Issue",
|
|
853
|
+
"settings.npm": "Install via npm",
|
|
854
|
+
"settings.label": "Settings",
|
|
855
|
+
"settings.close": "Close",
|
|
856
|
+
"settings.section.features": "Features & Shortcuts",
|
|
857
|
+
"settings.section.personal": "Personalization",
|
|
858
|
+
"settings.section.focus": "Focus",
|
|
859
|
+
"settings.personal.hint": "Dot size, accent color, and position — saved as you adjust",
|
|
860
|
+
"settings.personal.expand": "Expand personalization",
|
|
861
|
+
"settings.personal.collapse": "Collapse personalization",
|
|
862
|
+
"settings.personal.summary": "{accent} · Icon {icon}px · {side}",
|
|
863
|
+
"settings.desc.search": "Filter by full message text and jump to the match",
|
|
864
|
+
"settings.desc.list": "Overview of every prompt in this session",
|
|
865
|
+
"settings.desc.sessionSearch": "Search across all sessions",
|
|
866
|
+
"settings.desc.bookmarks": "Show bookmarked messages only",
|
|
867
|
+
"settings.desc.focus": "Dim AI thinking blocks for a cleaner read",
|
|
868
|
+
"settings.desc.updateCheck": "Check npm for a newer release",
|
|
869
|
+
"settings.desc.settings": "Customize the toolbar and appearance",
|
|
870
|
+
"settings.accent": "Accent color",
|
|
871
|
+
"settings.custom": "Custom",
|
|
872
|
+
"settings.iconSize": "Icon / dot size",
|
|
873
|
+
"settings.inset": "Distance from the edge",
|
|
874
|
+
"settings.side": "Position",
|
|
875
|
+
"settings.side.left": "Left",
|
|
876
|
+
"settings.side.right": "Right",
|
|
877
|
+
"settings.focus.hint": "Combine these options into your own focus recipe; the eye button on the toolbar stays the master switch",
|
|
878
|
+
"settings.focus.expand": "Expand focus settings",
|
|
879
|
+
"settings.focus.collapse": "Collapse focus settings",
|
|
880
|
+
"settings.focus.dimThink": "Dim think reasoning",
|
|
881
|
+
"settings.focus.dimTools": "Dim tool call cards",
|
|
882
|
+
"settings.focus.collapseThink": "Collapse think",
|
|
883
|
+
"settings.focus.opacity": "Dim strength",
|
|
884
|
+
"settings.focus.summary": "{opts} · Strength {opacity}%",
|
|
885
|
+
"settings.focus.summary.think": "Think dim",
|
|
886
|
+
"settings.focus.summary.tools": "Tools dim",
|
|
887
|
+
"settings.focus.summary.collapse": "Think collapse",
|
|
888
|
+
"settings.focus.summary.none": "Off",
|
|
889
|
+
"update.check": "Check updates",
|
|
890
|
+
"update.title": "Update check",
|
|
891
|
+
"update.current": "Current version",
|
|
892
|
+
"update.latest": "Latest version",
|
|
893
|
+
"update.upToDate": "You are up to date",
|
|
894
|
+
"update.available": "Update available",
|
|
895
|
+
"update.goNpm": "Upgrade on npm",
|
|
896
|
+
"update.hostLines": "Supported official version lines",
|
|
897
|
+
"update.checking": "Checking…",
|
|
898
|
+
"update.failed": "Check failed",
|
|
899
|
+
"update.retry": "Retry",
|
|
900
|
+
"settings.language": "Language",
|
|
901
|
+
"settings.lang.system": "Follow system",
|
|
902
|
+
"settings.lang.zh": "Chinese",
|
|
903
|
+
"settings.lang.en": "English"
|
|
904
|
+
};
|
|
905
|
+
/**
|
|
906
|
+
* Interpolate `{name}` placeholders with params, matching the harness t seat's
|
|
907
|
+
* substitution shape; an unknown parameter leaves the placeholder verbatim.
|
|
908
|
+
*/
|
|
909
|
+
function interpolate(template, params) {
|
|
910
|
+
if (params === void 0) return template;
|
|
911
|
+
return template.replace(/\{([A-Za-z][A-Za-z0-9]*)\}/g, (slot, name) => name in params ? String(params[name]) : slot);
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Dictionary-backed translate for the forced-language override (locale prefs
|
|
915
|
+
* 'zh' / 'en'): resolves a key against the plugin's own dictionaries with
|
|
916
|
+
* placeholder interpolation; unknown keys pass through unchanged (same
|
|
917
|
+
* degradation as the harness seat).
|
|
435
918
|
*/
|
|
436
|
-
function
|
|
437
|
-
|
|
919
|
+
function translateDict(dict, key, params) {
|
|
920
|
+
const template = dict[key];
|
|
921
|
+
return template === void 0 ? key : interpolate(template, params);
|
|
438
922
|
}
|
|
439
923
|
//#endregion
|
|
440
924
|
//#region src/client/turn-group-logic.ts
|
|
@@ -1300,20 +1784,36 @@ window.__ModuleLoader__.load({
|
|
|
1300
1784
|
//#endregion
|
|
1301
1785
|
//#region src/client/toolbar-prefs.ts
|
|
1302
1786
|
/**
|
|
1303
|
-
* toolbar-prefs: the persistence layer for the milestone rail's
|
|
1304
|
-
*
|
|
1787
|
+
* toolbar-prefs: the persistence layer for the milestone rail's toolbar
|
|
1788
|
+
* personalization — WHICH function keys stay visible outside the collapse
|
|
1789
|
+
* (pinned) plus the settings-module appearance prefs (accent color, icon/dot
|
|
1790
|
+
* size, distance from the rail's screen edge, and rail side).
|
|
1791
|
+
*
|
|
1792
|
+
* Storage contract: one localStorage key (`dsh-milestone.toolbar`) holding a
|
|
1793
|
+
* JSON object:
|
|
1305
1794
|
*
|
|
1306
|
-
*
|
|
1307
|
-
*
|
|
1308
|
-
*
|
|
1309
|
-
*
|
|
1795
|
+
* { "pinned": string[], "accent": "#rrggbb", "iconSize": number,
|
|
1796
|
+
* "inset": number, "side": "left" | "right", "locale": "system"|"zh"|"en",
|
|
1797
|
+
* "focus": { "dimThink": boolean, "dimTools": boolean,
|
|
1798
|
+
* "collapseThink": boolean, "opacity": number } }
|
|
1310
1799
|
*
|
|
1311
|
-
*
|
|
1312
|
-
* (
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1800
|
+
* Backward compatibility: the pre-personalization blob `{ "pinned": string[] }`
|
|
1801
|
+
* (and an entirely absent value) parses to the DEFAULT prefs with the new
|
|
1802
|
+
* fields at their defaults — old users keep their pins untouched. The same
|
|
1803
|
+
* rule covers the `focus` object: a blob stored before 0.6.3 (no `focus`
|
|
1804
|
+
* field) gains the default focus mix.
|
|
1805
|
+
*
|
|
1806
|
+
* All reads are sanitized per field:
|
|
1807
|
+
* - `pinned`: whitelisted ids only (`TOOLBAR_PIN_IDS`), duplicates dropped,
|
|
1808
|
+
* first-seen (pin) order preserved;
|
|
1809
|
+
* - `accent`: a canonical `#rrggbb` hex, lowercased; anything else falls
|
|
1810
|
+
* back to the default blue;
|
|
1811
|
+
* - `iconSize` / `inset`: finite numbers snapped to the slider step
|
|
1812
|
+
* (even values) and clamped to the slider range;
|
|
1813
|
+
* - `side`: exactly `'left'` or `'right'`;
|
|
1814
|
+
* - `focus`: three booleans (`dimThink` / `dimTools` / `collapseThink`)
|
|
1815
|
+
* defaulting to `true` / `false` / `false`, plus the dim `opacity`
|
|
1816
|
+
* snapped to the 0.1 step and clamped to [0.2, 0.8].
|
|
1317
1817
|
*
|
|
1318
1818
|
* The whitelist lives HERE (not in MilestoneRail) so the pure functions stay
|
|
1319
1819
|
* dependency-free and unit-testable; MilestoneRail's feature registry keys
|
|
@@ -1323,8 +1823,10 @@ window.__ModuleLoader__.load({
|
|
|
1323
1823
|
const TOOLBAR_PREFS_KEY = "dsh-milestone.toolbar";
|
|
1324
1824
|
/**
|
|
1325
1825
|
* Canonical function-key ids that may be pinned outside the collapse, in
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1826
|
+
* render order. `settings` is a REGULAR feature since the B-design move: the
|
|
1827
|
+
* gear left the always-visible chrome and now sits at the end of the expanded
|
|
1828
|
+
* feature queue (default unpinned). Adding a feature here (plus its registry
|
|
1829
|
+
* entry in MilestoneRail) is the whole "pin it" extension point.
|
|
1328
1830
|
*/
|
|
1329
1831
|
const TOOLBAR_PIN_IDS = [
|
|
1330
1832
|
"search",
|
|
@@ -1332,32 +1834,41 @@ window.__ModuleLoader__.load({
|
|
|
1332
1834
|
"sessionSearch",
|
|
1333
1835
|
"bookmarks",
|
|
1334
1836
|
"focus",
|
|
1335
|
-
"updateCheck"
|
|
1837
|
+
"updateCheck",
|
|
1838
|
+
"settings"
|
|
1336
1839
|
];
|
|
1840
|
+
/** The default accent (the classic milestone blue). */
|
|
1841
|
+
const DEFAULT_ACCENT = "#4d7cfd";
|
|
1842
|
+
/** Slider domain for the focus dim strength (0.2 = 20% .. 0.8 = 80%). */
|
|
1843
|
+
const FOCUS_OPACITY_MIN = .2;
|
|
1844
|
+
const FOCUS_OPACITY_MAX = .8;
|
|
1845
|
+
/** The canonical default focus mix (dim think at 40%; tools/collapse off). */
|
|
1846
|
+
const DEFAULT_FOCUS_PREFS = {
|
|
1847
|
+
dimThink: true,
|
|
1848
|
+
dimTools: false,
|
|
1849
|
+
collapseThink: false,
|
|
1850
|
+
opacity: .4
|
|
1851
|
+
};
|
|
1852
|
+
/** The canonical default prefs ("恢复默认" target; also the read fallback). */
|
|
1853
|
+
const DEFAULT_PREFS = {
|
|
1854
|
+
pinned: [],
|
|
1855
|
+
accent: DEFAULT_ACCENT,
|
|
1856
|
+
iconSize: 28,
|
|
1857
|
+
inset: 14,
|
|
1858
|
+
side: "right",
|
|
1859
|
+
locale: "system",
|
|
1860
|
+
focus: { ...DEFAULT_FOCUS_PREFS }
|
|
1861
|
+
};
|
|
1337
1862
|
/** Type guard for registry ids — unknown strings never survive a parse. */
|
|
1338
1863
|
function isToolbarPinId(id) {
|
|
1339
1864
|
return TOOLBAR_PIN_IDS.includes(id);
|
|
1340
1865
|
}
|
|
1341
|
-
/**
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
* and duplicates are dropped; the result keeps the caller's first-seen
|
|
1345
|
-
* (pin) order.
|
|
1346
|
-
*/
|
|
1347
|
-
function parsePrefs(raw) {
|
|
1348
|
-
if (raw === null) return [];
|
|
1349
|
-
let parsed;
|
|
1350
|
-
try {
|
|
1351
|
-
parsed = JSON.parse(raw);
|
|
1352
|
-
} catch {
|
|
1353
|
-
return [];
|
|
1354
|
-
}
|
|
1355
|
-
if (typeof parsed !== "object" || parsed === null) return [];
|
|
1356
|
-
const { pinned } = parsed;
|
|
1357
|
-
if (!Array.isArray(pinned)) return [];
|
|
1866
|
+
/** Whitelist + dedupe + first-seen-order sanitizer for the pinned list. */
|
|
1867
|
+
function sanitizePinned(raw) {
|
|
1868
|
+
if (!Array.isArray(raw)) return [];
|
|
1358
1869
|
const seen = /* @__PURE__ */ new Set();
|
|
1359
1870
|
const result = [];
|
|
1360
|
-
for (const id of
|
|
1871
|
+
for (const id of raw) {
|
|
1361
1872
|
if (typeof id !== "string" || !isToolbarPinId(id) || seen.has(id)) continue;
|
|
1362
1873
|
seen.add(id);
|
|
1363
1874
|
result.push(id);
|
|
@@ -1365,42 +1876,104 @@ window.__ModuleLoader__.load({
|
|
|
1365
1876
|
return result;
|
|
1366
1877
|
}
|
|
1367
1878
|
/**
|
|
1879
|
+
* Snap a finite number to the nearest `step` inside [min, max]; any non-finite
|
|
1880
|
+
* or non-number input falls back to `fallback`. Used for both sliders so a
|
|
1881
|
+
* hand-edited blob (e.g. `iconSize: 21`) converges on a legal slider value.
|
|
1882
|
+
*/
|
|
1883
|
+
function clampStep(value, min, max, step, fallback) {
|
|
1884
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
1885
|
+
const snapped = Math.round(Math.min(max, Math.max(min, value)) / step) * step;
|
|
1886
|
+
return Math.min(max, Math.max(min, snapped));
|
|
1887
|
+
}
|
|
1888
|
+
/** Boolean sanitizer for the focus mix flags: only `true`/`false` survive. */
|
|
1889
|
+
function sanitizeBoolean(value, fallback) {
|
|
1890
|
+
return typeof value === "boolean" ? value : fallback;
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Focus-mix sanitizer: `dimTools`/`collapseThink` default off, `dimThink`
|
|
1894
|
+
* defaults ON (the classic pre-0.6.3 behavior), and `opacity` snaps to the
|
|
1895
|
+
* 0.1 step inside [0.2, 0.8]. Snapping works in tenths (step × 10 = integer)
|
|
1896
|
+
* instead of a raw division so hand-edited floats like `0.30000000000000004`
|
|
1897
|
+
* always converge on exactly `0.3`.
|
|
1898
|
+
*/
|
|
1899
|
+
function sanitizeFocus(raw) {
|
|
1900
|
+
if (typeof raw !== "object" || raw === null) return { ...DEFAULT_FOCUS_PREFS };
|
|
1901
|
+
const { dimThink, dimTools, collapseThink, opacity } = raw;
|
|
1902
|
+
const clamped = typeof opacity === "number" && Number.isFinite(opacity) ? Math.min(FOCUS_OPACITY_MAX, Math.max(FOCUS_OPACITY_MIN, opacity)) : DEFAULT_FOCUS_PREFS.opacity;
|
|
1903
|
+
const tenths = Math.round(clamped * 10);
|
|
1904
|
+
const snapped = Math.min(FOCUS_OPACITY_MAX, Math.max(FOCUS_OPACITY_MIN, tenths / 10));
|
|
1905
|
+
return {
|
|
1906
|
+
dimThink: sanitizeBoolean(dimThink, DEFAULT_FOCUS_PREFS.dimThink),
|
|
1907
|
+
dimTools: sanitizeBoolean(dimTools, DEFAULT_FOCUS_PREFS.dimTools),
|
|
1908
|
+
collapseThink: sanitizeBoolean(collapseThink, DEFAULT_FOCUS_PREFS.collapseThink),
|
|
1909
|
+
opacity: snapped
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Parse + sanitize the raw persisted blob: `null` (nothing stored), invalid
|
|
1914
|
+
* JSON, or a non-object shape all degrade to the DEFAULT prefs. Each field is
|
|
1915
|
+
* sanitized independently, so a half-corrupt blob keeps its valid parts
|
|
1916
|
+
* (e.g. an old `{pinned}`-only blob gains the default accent/size/inset/side
|
|
1917
|
+
* AND the default focus mix).
|
|
1918
|
+
*/
|
|
1919
|
+
function parsePrefs(raw) {
|
|
1920
|
+
if (raw === null) return { ...DEFAULT_PREFS };
|
|
1921
|
+
let parsed;
|
|
1922
|
+
try {
|
|
1923
|
+
parsed = JSON.parse(raw);
|
|
1924
|
+
} catch {
|
|
1925
|
+
return { ...DEFAULT_PREFS };
|
|
1926
|
+
}
|
|
1927
|
+
if (typeof parsed !== "object" || parsed === null) return { ...DEFAULT_PREFS };
|
|
1928
|
+
const { pinned, accent, iconSize, inset, side, locale, focus } = parsed;
|
|
1929
|
+
return {
|
|
1930
|
+
pinned: sanitizePinned(pinned),
|
|
1931
|
+
accent: typeof accent === "string" && isHexColor(accent) ? accent.toLowerCase() : DEFAULT_PREFS.accent,
|
|
1932
|
+
iconSize: clampStep(iconSize, 20, 36, 2, DEFAULT_PREFS.iconSize),
|
|
1933
|
+
inset: clampStep(inset, 0, 40, 2, DEFAULT_PREFS.inset),
|
|
1934
|
+
side: side === "left" || side === "right" ? side : DEFAULT_PREFS.side,
|
|
1935
|
+
locale: locale === "zh" || locale === "en" || locale === "system" ? locale : DEFAULT_PREFS.locale,
|
|
1936
|
+
focus: sanitizeFocus(focus)
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1368
1940
|
* Read + sanitize the persisted toolbar prefs from localStorage. Degrades to
|
|
1369
|
-
*
|
|
1370
|
-
* best-effort enhancement, never a render blocker.
|
|
1941
|
+
* the DEFAULT prefs when storage is unavailable (SSR, sandboxed iframe) —
|
|
1942
|
+
* personalization is a best-effort enhancement, never a render blocker.
|
|
1371
1943
|
*/
|
|
1372
1944
|
function loadPrefs() {
|
|
1373
1945
|
try {
|
|
1374
1946
|
return parsePrefs(localStorage.getItem(TOOLBAR_PREFS_KEY));
|
|
1375
1947
|
} catch {
|
|
1376
|
-
return
|
|
1948
|
+
return { ...DEFAULT_PREFS };
|
|
1377
1949
|
}
|
|
1378
1950
|
}
|
|
1379
1951
|
/**
|
|
1380
|
-
* Persist the
|
|
1381
|
-
* value is never written).
|
|
1382
|
-
*
|
|
1383
|
-
* rejected. Swallows storage failures for the same best-effort reason as
|
|
1384
|
-
* {@link loadPrefs}.
|
|
1952
|
+
* Persist the full prefs (sanitized on the way out so a corrupt in-memory
|
|
1953
|
+
* value is never written). Swallows storage failures for the same best-effort
|
|
1954
|
+
* reason as {@link loadPrefs}.
|
|
1385
1955
|
*/
|
|
1386
|
-
function savePrefs(
|
|
1387
|
-
const cleaned = parsePrefs(JSON.stringify(
|
|
1956
|
+
function savePrefs(prefs) {
|
|
1957
|
+
const cleaned = parsePrefs(JSON.stringify(prefs));
|
|
1388
1958
|
try {
|
|
1389
|
-
localStorage.setItem(TOOLBAR_PREFS_KEY, JSON.stringify(
|
|
1959
|
+
localStorage.setItem(TOOLBAR_PREFS_KEY, JSON.stringify(cleaned));
|
|
1390
1960
|
} catch {}
|
|
1391
1961
|
}
|
|
1392
1962
|
/**
|
|
1393
1963
|
* Pure toggle: adds `id` to the pinned set when absent, removes it when
|
|
1394
|
-
* present. Unknown ids are ignored (
|
|
1395
|
-
*
|
|
1964
|
+
* present. Unknown ids are ignored (prefs returned unchanged) and the pinned
|
|
1965
|
+
* set is always deduped via the sanitizer, so callers can feed the result
|
|
1396
1966
|
* straight back into {@link savePrefs}.
|
|
1397
1967
|
*/
|
|
1398
|
-
function togglePin(
|
|
1399
|
-
if (!isToolbarPinId(id)) return
|
|
1400
|
-
const next = new Set(pinned);
|
|
1968
|
+
function togglePin(prefs, id) {
|
|
1969
|
+
if (!isToolbarPinId(id)) return { ...prefs };
|
|
1970
|
+
const next = new Set(prefs.pinned);
|
|
1401
1971
|
if (next.has(id)) next.delete(id);
|
|
1402
1972
|
else next.add(id);
|
|
1403
|
-
return
|
|
1973
|
+
return {
|
|
1974
|
+
...prefs,
|
|
1975
|
+
pinned: sanitizePinned([...next])
|
|
1976
|
+
};
|
|
1404
1977
|
}
|
|
1405
1978
|
//#endregion
|
|
1406
1979
|
//#region src/client/version-logic.ts
|
|
@@ -1706,12 +2279,12 @@ window.__ModuleLoader__.load({
|
|
|
1706
2279
|
* Installed plugin version. Injected at build time as
|
|
1707
2280
|
* `__DSH_MILESTONE_VERSION__`; falls back to `0.0.0-dev` when unbuilt.
|
|
1708
2281
|
*/
|
|
1709
|
-
const PLUGIN_VERSION = "0.6.
|
|
2282
|
+
const PLUGIN_VERSION = "0.6.3";
|
|
1710
2283
|
//#endregion
|
|
1711
2284
|
//#region src/client/MilestoneRail.tsx
|
|
1712
2285
|
/**
|
|
1713
2286
|
* MilestoneRail: the milestone.rail entry (session scope). Renders a fixed
|
|
1714
|
-
*
|
|
2287
|
+
* side vertical scrubber as a **fixed-pitch dot list** (like a git commit
|
|
1715
2288
|
* graph), NOT a minimap: one dot per user message, equal spacing regardless of
|
|
1716
2289
|
* conversation length. The list itself scrolls with the wheel when it outgrows
|
|
1717
2290
|
* the viewport; hovering a dot shows rich metadata (time, turn, duration, end
|
|
@@ -1723,14 +2296,16 @@ window.__ModuleLoader__.load({
|
|
|
1723
2296
|
* and the ui-conversation 'turn-tail'
|
|
1724
2297
|
* location data (ttftMs/tokensPerSecond)
|
|
1725
2298
|
*
|
|
1726
|
-
* Positioning: the rail hugs the conversation scrollport's
|
|
1727
|
-
*
|
|
2299
|
+
* Positioning: the rail hugs the conversation scrollport's chosen screen edge
|
|
2300
|
+
* (settings 位置: left or right), offset a little inward so it clears the
|
|
2301
|
+
* native scrollbar and sits near the prose.
|
|
1728
2302
|
*
|
|
1729
2303
|
* In-rail search (F1): a magnifier toggle at the rail top opens a compact
|
|
1730
|
-
* panel
|
|
1731
|
-
* the dots (non-matches dim), Enter cycles the active match
|
|
1732
|
-
* jumps to it, Escape clears and closes. Matching runs over the
|
|
1733
|
-
* text (`text` from rail-logic.extractText), not the truncated
|
|
2304
|
+
* panel on the rail's free side with a message-text search input; matches
|
|
2305
|
+
* light up the dots (non-matches dim), Enter cycles the active match
|
|
2306
|
+
* (wrapping) and jumps to it, Escape clears and closes. Matching runs over the
|
|
2307
|
+
* FULL message text (`text` from rail-logic.extractText), not the truncated
|
|
2308
|
+
* hover preview.
|
|
1734
2309
|
*
|
|
1735
2310
|
* Current-position highlight (F2): the dot for the user message at/just above
|
|
1736
2311
|
* the conversation viewport top carries a white ring (`useCurrentAnchor`
|
|
@@ -1739,8 +2314,15 @@ window.__ModuleLoader__.load({
|
|
|
1739
2314
|
* Load-older + window coverage (F3): when the session still has earlier pages
|
|
1740
2315
|
* (`hasMore`) a slim `···` button sits at the rail top and triggers the
|
|
1741
2316
|
* injected `loadOlder` action (disabled + `data-loading-older` while
|
|
1742
|
-
* `loadingOlder`), and a compact hint
|
|
2317
|
+
* `loadingOlder`), and a compact hint on the rail's free side states how many
|
|
1743
2318
|
* messages the current window covers.
|
|
2319
|
+
*
|
|
2320
|
+
* Settings (B-design): the gear is a REGULAR toolbar feature ("settings",
|
|
2321
|
+
* registry last, default unpinned) — the collapsed rail shows only the expand
|
|
2322
|
+
* arrow plus the user's pinned keys, and expanding reveals the gear at the end
|
|
2323
|
+
* of the queue. The gear opens a CENTERED modal dialog (function-key pins /
|
|
2324
|
+
* hover descriptions, the personalization controls, and the support-us card
|
|
2325
|
+
* grid); everything the modal changes persists under `dsh-milestone.toolbar`.
|
|
1744
2326
|
*/
|
|
1745
2327
|
/** Minimum user messages before the rail adds value. */
|
|
1746
2328
|
const MIN_MARKS = 2;
|
|
@@ -1749,36 +2331,211 @@ window.__ModuleLoader__.load({
|
|
|
1749
2331
|
const NO_BOOKMARKS = [];
|
|
1750
2332
|
/** Stable no-kinds fallback for marks whose turn carries no badge nodes. */
|
|
1751
2333
|
const NO_KINDS = [];
|
|
1752
|
-
/**
|
|
1753
|
-
* Self-contained pulse keyframes for the transient badges (running/awaiting):
|
|
1754
|
-
* an expanding currentColor ring on box-shadow plus an opacity beat, driven by
|
|
1755
|
-
* `animation` on the badge ring span (kept in an inline <style> so the plugin
|
|
1756
|
-
* stays zero-asset).
|
|
1757
|
-
*/
|
|
1758
|
-
const BADGE_PULSE_CSS = `@keyframes milestone-badge-pulse {
|
|
1759
|
-
0% { box-shadow: 0 0 0 0 currentColor; opacity: 0.85 }
|
|
1760
|
-
70% { box-shadow: 0 0 0 5px transparent; opacity: 0.35 }
|
|
1761
|
-
100% { box-shadow: 0 0 0 0 transparent; opacity: 0.85 }
|
|
1762
|
-
}`;
|
|
1763
|
-
/**
|
|
1764
|
-
* P3 focus mode: dims the harness's AI thinking/scratchpad blocks so the
|
|
1765
|
-
* conversation reads cleaner. The rule targets the stable, un-hashed
|
|
1766
|
-
* `data-variant="think"` attribute on the thinking-block ROOT (the harness
|
|
1767
|
-
* renders it as `data-variant="think"` with `data-state="running|ok"`), so an
|
|
1768
|
-
* overlay plugin can dim it with plain CSS. Hovering a dimmed block (or
|
|
1769
|
-
* opening it, `[data-open]`) restores full opacity. Kept in an inline
|
|
1770
|
-
* <style> so the plugin stays zero-asset — same pattern as BADGE_PULSE_CSS.
|
|
1771
|
-
*/
|
|
1772
|
-
const FOCUS_CSS = `[data-variant="think"] { opacity: 0.4; transition: opacity 0.2s; }
|
|
1773
|
-
[data-variant="think"]:hover, [data-variant="think"] [data-open] { opacity: 1; }`;
|
|
1774
|
-
/** Visual dot diameter (px). */
|
|
2334
|
+
/** Visual dot diameter at the default icon size (px). */
|
|
1775
2335
|
const DOT_SIZE = 14;
|
|
1776
|
-
/** Hit area per dot (px) — larger than the dot
|
|
2336
|
+
/** Hit area per dot at the default icon size (px) — larger than the dot. */
|
|
1777
2337
|
const DOT_HIT = 28;
|
|
1778
|
-
/** Vertical gap between dot hit areas
|
|
2338
|
+
/** Vertical gap between dot hit areas at the default icon size (px). */
|
|
1779
2339
|
const DOT_GAP = 14;
|
|
1780
|
-
/**
|
|
1781
|
-
|
|
2340
|
+
/**
|
|
2341
|
+
* Extra top margin a new turn group's FIRST dot gets (replaces the old
|
|
2342
|
+
* `data-turn-separator` line): same-group pitch stays DOT_GAP, a group
|
|
2343
|
+
* boundary opens another GROUP_GAP_EXTRA px (14 → 18 at default size),
|
|
2344
|
+
* expressed purely as spacing — no line element.
|
|
2345
|
+
*/
|
|
2346
|
+
const GROUP_GAP_EXTRA = 4;
|
|
2347
|
+
/**
|
|
2348
|
+
* Preset accent swatches for the settings 强调色 row (default blue first).
|
|
2349
|
+
* The custom color input accepts any #rrggbb.
|
|
2350
|
+
*/
|
|
2351
|
+
const ACCENT_PRESETS = [
|
|
2352
|
+
"#4d7cfd",
|
|
2353
|
+
"#22c55e",
|
|
2354
|
+
"#f59e0b",
|
|
2355
|
+
"#ef4444",
|
|
2356
|
+
"#a855f7",
|
|
2357
|
+
"#06b6d4",
|
|
2358
|
+
"#ec4899",
|
|
2359
|
+
"#f97316"
|
|
2360
|
+
];
|
|
2361
|
+
/** Known floating-panel widths (px) used to anchor side=left panels to the
|
|
2362
|
+
* rail's free (right) side — the panel components take a viewport `right`
|
|
2363
|
+
* offset, so a left rail must back-calculate it from the panel width. */
|
|
2364
|
+
const PANEL_WIDTH_SEARCH = 220;
|
|
2365
|
+
const PANEL_WIDTH_STANDARD = 280;
|
|
2366
|
+
/** Tooltip anchor width: its maxWidth cap, so a tooltip never overlaps the rail. */
|
|
2367
|
+
const TOOLTIP_ANCHOR_WIDTH = 300;
|
|
2368
|
+
/**
|
|
2369
|
+
* P3 focus mode (0.6.3: user-tuned "聚焦搭配"): when the eye toggle is armed,
|
|
2370
|
+
* an inline <style> (zero-asset, same pattern as the original FOCUS_CSS)
|
|
2371
|
+
* dims/collapses the harness content classes the USER opted into, at the
|
|
2372
|
+
* strength the user picked. Which content to dim and whether to additionally
|
|
2373
|
+
* collapse think is a persisted `prefs.focus` mix; the master on/off switch
|
|
2374
|
+
* stays the toolbar eye button.
|
|
2375
|
+
*
|
|
2376
|
+
* STABLE SELECTORS — researched against the rc.2 official build products:
|
|
2377
|
+
*
|
|
2378
|
+
* - think: `dsh-client-ui-conversation` renders every assistant reasoning
|
|
2379
|
+
* disclosure as a root `div[data-variant="think"][data-state="running|ok"]`
|
|
2380
|
+
* (ReasoningRow). The reasoning body lives INSIDE that root — the
|
|
2381
|
+
* DisclosureRow's children — so the root is a single clampable container:
|
|
2382
|
+
* CSS `max-height` + `overflow: hidden` collapses exactly the body while
|
|
2383
|
+
* the header row ("Think · summary") stays visible. → collapseThink is
|
|
2384
|
+
* feasible with pure CSS (no JS, no harness-internal interaction).
|
|
2385
|
+
*
|
|
2386
|
+
* - tool calls: `dsh-client-ui-tool` wraps EVERY atomic call (all ToolRow
|
|
2387
|
+
* presentation variants AND the bash-sample row) in
|
|
2388
|
+
* `div[data-chat-call-id]` (with `data-chat-anchor-key="call:<callId>"`);
|
|
2389
|
+
* no other card type in the harness uses that attribute (verified across
|
|
2390
|
+
* the whole @deepseek-ai install). → `[data-chat-call-id]` is the stable,
|
|
2391
|
+
* variant-independent tool-call-card selector.
|
|
2392
|
+
*
|
|
2393
|
+
* Hover/open restore mirrors the classic rule: `:hover` restores, and the
|
|
2394
|
+
* DisclosureRow inside the collapsed target sets `[data-open]` when the user
|
|
2395
|
+
* opens it (the DESCENDANT form `target [data-open]` is required — same as
|
|
2396
|
+
* pre-0.6.3). The collapse strip releases on hover AND on `[data-open]` so
|
|
2397
|
+
* an opened think disclosure never stays crushed.
|
|
2398
|
+
*/
|
|
2399
|
+
const FOCUS_SELECTOR_THINK = "[data-variant=\"think\"]";
|
|
2400
|
+
const FOCUS_SELECTOR_TOOL = "[data-chat-call-id]";
|
|
2401
|
+
/** Restored height when hovering/opening a collapsed think disclosure. */
|
|
2402
|
+
const FOCUS_THINK_MAX_HEIGHT = "78vh";
|
|
2403
|
+
/**
|
|
2404
|
+
* Compose the focus-mode stylesheet from the persisted focus mix. Pure —
|
|
2405
|
+
* exported so unit tests can pin the exact rule text. `''` when no option is
|
|
2406
|
+
* armed (the master switch simply injects nothing).
|
|
2407
|
+
*/
|
|
2408
|
+
function buildFocusCss(focus) {
|
|
2409
|
+
const { dimThink, dimTools, collapseThink } = focus;
|
|
2410
|
+
const strength = focus.opacity.toFixed(1);
|
|
2411
|
+
const rules = [];
|
|
2412
|
+
if (dimThink || collapseThink) {
|
|
2413
|
+
const decls = [];
|
|
2414
|
+
if (dimThink) decls.push(`opacity: ${strength}`);
|
|
2415
|
+
if (collapseThink) decls.push(`max-height: 36px`, "overflow: hidden");
|
|
2416
|
+
rules.push(`${FOCUS_SELECTOR_THINK} { ${decls.join("; ")}; transition: opacity 0.2s${collapseThink ? ", max-height 0.2s" : ""}; }`);
|
|
2417
|
+
rules.push(`${FOCUS_SELECTOR_THINK}:hover, ${FOCUS_SELECTOR_THINK} [data-open] { opacity: 1;${collapseThink ? ` max-height: ${FOCUS_THINK_MAX_HEIGHT};` : ""} }`);
|
|
2418
|
+
}
|
|
2419
|
+
if (dimTools) {
|
|
2420
|
+
rules.push(`${FOCUS_SELECTOR_TOOL} { opacity: ${strength}; transition: opacity 0.2s; }`);
|
|
2421
|
+
rules.push(`${FOCUS_SELECTOR_TOOL}:hover, ${FOCUS_SELECTOR_TOOL} [data-open] { opacity: 1; }`);
|
|
2422
|
+
}
|
|
2423
|
+
return rules.join("\n");
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Settings modal shared palette + geometry — one source for the inline
|
|
2427
|
+
* styles AND the static MODAL_CSS block below so they cannot drift. Dark
|
|
2428
|
+
* panel on a dark host, three text tiers (primary / section title / hint +
|
|
2429
|
+
* muted), one border tone, a 12px panel / 8px control radius scale and a
|
|
2430
|
+
* 4-unit spacing scale (4 / 8 / 12 / 16 / 20).
|
|
2431
|
+
*/
|
|
2432
|
+
const MODAL_BG = "rgba(20, 24, 32, 0.98)";
|
|
2433
|
+
const MODAL_FG = "#e6e8ee";
|
|
2434
|
+
const MODAL_TITLE = "#c7cede";
|
|
2435
|
+
const MODAL_TEXT = "#b9c2d4";
|
|
2436
|
+
const MODAL_HINT = "#8b96ab";
|
|
2437
|
+
const MODAL_BORDER = "rgba(255, 255, 255, 0.14)";
|
|
2438
|
+
const MODAL_TIP_BG = "#222834";
|
|
2439
|
+
const MODAL_RADIUS_PANEL = 12;
|
|
2440
|
+
const MODAL_RADIUS_CONTROL = 8;
|
|
2441
|
+
/** Near-row description tip: how far its right edge sits from the row's right
|
|
2442
|
+
* edge — clears the 32px switch + its 10px padding. */
|
|
2443
|
+
const MODAL_TIP_RIGHT = 54;
|
|
2444
|
+
/**
|
|
2445
|
+
* Static settings-modal styling that needs `:hover`/`:focus-visible` (which
|
|
2446
|
+
* inline styles cannot express): the support-us card grid (micro-lift +
|
|
2447
|
+
* accent highlight), the pin-row / personal-header hover washes, one accent
|
|
2448
|
+
* focus ring for every modal control, the near-row tip's little arrow, the
|
|
2449
|
+
* personalization body's reveal, and a themed thin scrollbar. Accent values
|
|
2450
|
+
* come from the rail root's CSS variables (`--ms-accent*`), so one block
|
|
2451
|
+
* serves every accent. Inline styles keep these elements background-free
|
|
2452
|
+
* (except where noted) so the `:hover` washes below actually win. The
|
|
2453
|
+
* search-toggle recolor rule makes the EXTERNAL RailSearchUi chrome follow
|
|
2454
|
+
* the accent too (that file is owned by an earlier phase and cannot change);
|
|
2455
|
+
* `!important` is required because that toggle's own inline styles win
|
|
2456
|
+
* otherwise.
|
|
2457
|
+
*/
|
|
2458
|
+
const MODAL_CSS = `
|
|
2459
|
+
[data-support-card] {
|
|
2460
|
+
display: flex; align-items: center; justify-content: center; gap: 8px;
|
|
2461
|
+
padding: 10px 12px; border-radius: 8px;
|
|
2462
|
+
background: rgba(255, 255, 255, 0.05); border: 1px solid ${MODAL_BORDER};
|
|
2463
|
+
color: #c7cede; text-decoration: none; font-size: 12.5px; line-height: 1.4;
|
|
2464
|
+
transition: transform 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
2465
|
+
}
|
|
2466
|
+
[data-support-card]:hover { transform: translateY(-2px); border-color: var(--ms-accent); background: rgba(255, 255, 255, 0.09); }
|
|
2467
|
+
/* Row and header washes (the inline styles deliberately leave backgrounds
|
|
2468
|
+
unset so these rules win over the default padding-box background). */
|
|
2469
|
+
[data-toolbar-pin-toggle]:hover, [data-toolbar-pin-toggle]:focus-visible { background: rgba(255, 255, 255, 0.06); }
|
|
2470
|
+
[data-personal-toggle]:hover, [data-focus-toggle-settings]:hover { background: rgba(255, 255, 255, 0.05); }
|
|
2471
|
+
[data-focus-option]:hover { background: rgba(255, 255, 255, 0.04); }
|
|
2472
|
+
[data-toolbar-settings-close]:hover { background: rgba(255, 255, 255, 0.08); }
|
|
2473
|
+
[data-toolbar-settings-reset] { background: rgba(255, 255, 255, 0.06); }
|
|
2474
|
+
[data-toolbar-settings-reset]:hover { background: rgba(255, 255, 255, 0.1); }
|
|
2475
|
+
/* ONE accent ring for keyboard focus on every modal control. */
|
|
2476
|
+
[data-toolbar-pin-toggle]:focus-visible, [data-personal-toggle]:focus-visible,
|
|
2477
|
+
[data-focus-toggle-settings]:focus-visible,
|
|
2478
|
+
[data-toolbar-settings-close]:focus-visible, [data-toolbar-settings-reset]:focus-visible {
|
|
2479
|
+
box-shadow: 0 0 0 2px var(--ms-accent-soft);
|
|
2480
|
+
}
|
|
2481
|
+
/* Near-row description tip: a rotated square peeks out of the LEFT edge so
|
|
2482
|
+
the apex points back at the row's label. */
|
|
2483
|
+
[data-settings-tip]::before {
|
|
2484
|
+
content: ''; position: absolute; left: -3px; top: 50%;
|
|
2485
|
+
width: 7px; height: 7px; transform: translateY(-50%) rotate(45deg);
|
|
2486
|
+
background: ${MODAL_TIP_BG};
|
|
2487
|
+
border-left: 1px solid ${MODAL_BORDER};
|
|
2488
|
+
border-bottom: 1px solid ${MODAL_BORDER};
|
|
2489
|
+
}
|
|
2490
|
+
/* Tip + chevron motion lives here (not inline) so reduced-motion can kill it. */
|
|
2491
|
+
[data-settings-tip] { transition: opacity 140ms ease, transform 140ms ease, visibility 140ms; }
|
|
2492
|
+
[data-personal-toggle] svg, [data-focus-toggle-settings] svg { transition: transform 150ms ease; }
|
|
2493
|
+
/* One authored reveal: the personalization/focus bodies fade in on expand. */
|
|
2494
|
+
@keyframes ms-settings-fade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: none; } }
|
|
2495
|
+
[data-settings-personal-body], [data-settings-focus-body] { animation: ms-settings-fade 140ms ease; }
|
|
2496
|
+
/* Thin themed scrollbar for the scrollable modal panel. */
|
|
2497
|
+
[data-toolbar-settings-panel]::-webkit-scrollbar { width: 10px; }
|
|
2498
|
+
[data-toolbar-settings-panel]::-webkit-scrollbar-thumb {
|
|
2499
|
+
background: rgba(255, 255, 255, 0.16); border-radius: 6px; border: 3px solid transparent; background-clip: padding-box;
|
|
2500
|
+
}
|
|
2501
|
+
[data-toolbar-settings-panel]::-webkit-scrollbar-track { background: transparent; }
|
|
2502
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2503
|
+
[data-settings-tip], [data-personal-toggle] svg, [data-focus-toggle-settings] svg, [data-support-card] { transition: none; }
|
|
2504
|
+
[data-settings-personal-body], [data-settings-focus-body] { animation: none; }
|
|
2505
|
+
}
|
|
2506
|
+
[data-search-toggle] { color: #8b96ab !important; }
|
|
2507
|
+
[data-search-toggle][aria-pressed="true"] { background: var(--ms-accent-bg) !important; color: var(--ms-accent-soft) !important; }
|
|
2508
|
+
`;
|
|
2509
|
+
/**
|
|
2510
|
+
* Shared collapsible-section chrome (personalization + focus blocks): the
|
|
2511
|
+
* header button (chevron + title + live summary) and the summary text span.
|
|
2512
|
+
* One source so the two settings blocks cannot drift.
|
|
2513
|
+
*/
|
|
2514
|
+
const SECTION_TOGGLE_STYLE = {
|
|
2515
|
+
display: "flex",
|
|
2516
|
+
alignItems: "center",
|
|
2517
|
+
gap: 8,
|
|
2518
|
+
width: "100%",
|
|
2519
|
+
padding: "8px 10px",
|
|
2520
|
+
border: "none",
|
|
2521
|
+
borderRadius: MODAL_RADIUS_CONTROL,
|
|
2522
|
+
cursor: "pointer",
|
|
2523
|
+
color: MODAL_TITLE,
|
|
2524
|
+
fontSize: 13,
|
|
2525
|
+
fontWeight: 600,
|
|
2526
|
+
textAlign: "left"
|
|
2527
|
+
};
|
|
2528
|
+
const SECTION_SUMMARY_STYLE = {
|
|
2529
|
+
flex: 1,
|
|
2530
|
+
minWidth: 0,
|
|
2531
|
+
textAlign: "right",
|
|
2532
|
+
fontWeight: 400,
|
|
2533
|
+
fontSize: 12,
|
|
2534
|
+
color: MODAL_HINT,
|
|
2535
|
+
overflow: "hidden",
|
|
2536
|
+
textOverflow: "ellipsis",
|
|
2537
|
+
whiteSpace: "nowrap"
|
|
2538
|
+
};
|
|
1782
2539
|
/**
|
|
1783
2540
|
* P3 deep links (`#msg=<anchor-key>`): initial delay before the first
|
|
1784
2541
|
* deep-link attempt — the harness scrolls the conversation to the bottom on
|
|
@@ -1846,7 +2603,7 @@ window.__ModuleLoader__.load({
|
|
|
1846
2603
|
function MilestoneRail({ useSession, loadOlder, forkAt, useStore, actions, searchSessions = async () => ({
|
|
1847
2604
|
items: [],
|
|
1848
2605
|
hasMore: false
|
|
1849
|
-
}), openSession = () => {}, t = (key) => key }) {
|
|
2606
|
+
}), openSession = () => {}, t: frameworkT = (key) => key }) {
|
|
1850
2607
|
const order = useSession((s) => s.chat.order);
|
|
1851
2608
|
const nodes = useSession((s) => s.chat.nodes);
|
|
1852
2609
|
const locations = useSession((s) => s.chat.locations);
|
|
@@ -1951,46 +2708,85 @@ window.__ModuleLoader__.load({
|
|
|
1951
2708
|
}
|
|
1952
2709
|
return counts;
|
|
1953
2710
|
}, [displayMarks]);
|
|
1954
|
-
(0, react.
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
}
|
|
2711
|
+
const [prefs, setPrefs] = (0, react.useState)(() => loadPrefs());
|
|
2712
|
+
const { pinned, accent, iconSize, inset, side } = prefs;
|
|
2713
|
+
const scale = iconSize / DOT_HIT;
|
|
2714
|
+
const hit = iconSize;
|
|
2715
|
+
const size = DOT_SIZE * scale;
|
|
2716
|
+
const gap = DOT_GAP * scale;
|
|
2717
|
+
const accentSoft = lighten(accent, .42) ?? "#9db8ff";
|
|
2718
|
+
const accentBg = rgbaString(accent, .18) ?? "rgba(77, 124, 254, 0.18)";
|
|
2719
|
+
const accentStrong = rgbaString(accent, .55) ?? "rgba(77, 124, 254, 0.55)";
|
|
2720
|
+
/**
|
|
2721
|
+
* Language override (settings → 语言): `system` delegates to the harness
|
|
2722
|
+
* `t` seat (the framework-synthesized interpreter for the registered
|
|
2723
|
+
* `dsh-milestone` namespace); `zh`/`en` force the plugin's own dictionaries
|
|
2724
|
+
* so the rail copy switches independently of the host UI language. Every
|
|
2725
|
+
* call site below — rail chrome, panels, tooltip and the settings modal —
|
|
2726
|
+
* already resolves through this binding, so the override is global to the
|
|
2727
|
+
* rail without threading a second translate prop anywhere.
|
|
2728
|
+
*/
|
|
2729
|
+
const t = prefs.locale === "system" ? frameworkT : prefs.locale === "en" ? (key, params) => translateDict(en, key, params) : (key, params) => translateDict(zh, key, params);
|
|
2730
|
+
/** Write a patch of prefs through to state + localStorage. */
|
|
2731
|
+
const updatePrefs = (patch) => {
|
|
2732
|
+
setPrefs((prev) => {
|
|
2733
|
+
const next = {
|
|
2734
|
+
...prev,
|
|
2735
|
+
...patch
|
|
2736
|
+
};
|
|
2737
|
+
savePrefs(next);
|
|
2738
|
+
return next;
|
|
2739
|
+
});
|
|
2740
|
+
};
|
|
2741
|
+
/** 0.6.3: patch ONE focus-mix flag/strength (nested field, same write-through). */
|
|
2742
|
+
const updateFocus = (patch) => {
|
|
2743
|
+
updatePrefs({ focus: {
|
|
2744
|
+
...prefs.focus,
|
|
2745
|
+
...patch
|
|
2746
|
+
} });
|
|
2747
|
+
};
|
|
2748
|
+
/**
|
|
2749
|
+
* 0.6.3: the focus block's live summary — the armed options joined into a
|
|
2750
|
+
* "聚焦搭配" line, plus the strength percentage. e.g. `think 淡化 · 强度 40%`.
|
|
2751
|
+
*/
|
|
2752
|
+
const focusSummary = (() => {
|
|
2753
|
+
const parts = [
|
|
2754
|
+
prefs.focus.dimThink ? t("settings.focus.summary.think") : null,
|
|
2755
|
+
prefs.focus.dimTools ? t("settings.focus.summary.tools") : null,
|
|
2756
|
+
prefs.focus.collapseThink ? t("settings.focus.summary.collapse") : null
|
|
2757
|
+
].filter((part) => part !== null);
|
|
2758
|
+
return t("settings.focus.summary", {
|
|
2759
|
+
opts: parts.length > 0 ? parts.join(" · ") : t("settings.focus.summary.none"),
|
|
2760
|
+
opacity: Math.round(prefs.focus.opacity * 100)
|
|
2761
|
+
});
|
|
2762
|
+
})();
|
|
2763
|
+
/** B1: flip one feature's pin — state and the persisted blob update together. */
|
|
2764
|
+
const onTogglePin = (id) => {
|
|
2765
|
+
setPrefs((prev) => {
|
|
2766
|
+
const next = togglePin(prev, id);
|
|
2767
|
+
savePrefs(next);
|
|
2768
|
+
return next;
|
|
2769
|
+
});
|
|
2770
|
+
};
|
|
2771
|
+
/** B-design: 恢复默认 resets EVERYTHING — pins AND personalization. */
|
|
2772
|
+
const onResetAll = () => {
|
|
2773
|
+
const next = { ...DEFAULT_PREFS };
|
|
2774
|
+
setPrefs(next);
|
|
2775
|
+
savePrefs(next);
|
|
2776
|
+
};
|
|
1991
2777
|
const [toolbarExpanded, setToolbarExpanded] = (0, react.useState)(false);
|
|
2778
|
+
const [expandHovered, setExpandHovered] = (0, react.useState)(false);
|
|
2779
|
+
const [settingsHovered, setSettingsHovered] = (0, react.useState)(false);
|
|
1992
2780
|
const [settingsOpen, setSettingsOpen] = (0, react.useState)(false);
|
|
1993
|
-
|
|
2781
|
+
/** The feature whose near-row description tip is currently visible
|
|
2782
|
+
* (`null` = none — tips only appear on hover/focus of their own row). */
|
|
2783
|
+
const [descFeature, setDescFeature] = (0, react.useState)(null);
|
|
2784
|
+
/** B-design: the personalization block collapsess by default so only a
|
|
2785
|
+
* value summary leads the section; expanding reveals the controls. */
|
|
2786
|
+
const [personalOpen, setPersonalOpen] = (0, react.useState)(false);
|
|
2787
|
+
/** B-design (0.6.3): the focus block mirrors the personalization block —
|
|
2788
|
+
* collapsed by default, the header leads with a live option summary. */
|
|
2789
|
+
const [focusOpen, setFocusOpen] = (0, react.useState)(false);
|
|
1994
2790
|
const settingsRef = (0, react.useRef)(null);
|
|
1995
2791
|
const settingsBtnRef = (0, react.useRef)(null);
|
|
1996
2792
|
const [updateOpen, setUpdateOpen] = (0, react.useState)(false);
|
|
@@ -1998,11 +2794,12 @@ window.__ModuleLoader__.load({
|
|
|
1998
2794
|
const updatePanelRef = (0, react.useRef)(null);
|
|
1999
2795
|
const updateBtnRef = (0, react.useRef)(null);
|
|
2000
2796
|
/**
|
|
2001
|
-
* B1 settings
|
|
2797
|
+
* B1 settings modal: outside-pointerdown dismisses it (shared
|
|
2002
2798
|
* useOutsideDismiss contract) with focus returning to the gear afterwards.
|
|
2003
|
-
* The
|
|
2004
|
-
*
|
|
2005
|
-
*
|
|
2799
|
+
* The modal's full-screen overlay wraps the dialog, so a pointerdown on the
|
|
2800
|
+
* backdrop (or anywhere outside the dialog) closes it; the gear's own click
|
|
2801
|
+
* keeps its flip semantics through a `[data-toolbar-settings]` exclusion —
|
|
2802
|
+
* pointerdown on an armed gear must not double-close.
|
|
2006
2803
|
*/
|
|
2007
2804
|
useOutsideDismiss(settingsRef, settingsOpen, () => {
|
|
2008
2805
|
setSettingsOpen(false);
|
|
@@ -2018,6 +2815,10 @@ window.__ModuleLoader__.load({
|
|
|
2018
2815
|
window.addEventListener("keydown", onKey);
|
|
2019
2816
|
return () => window.removeEventListener("keydown", onKey);
|
|
2020
2817
|
}, [settingsOpen]);
|
|
2818
|
+
(0, react.useEffect)(() => {
|
|
2819
|
+
if (!settingsOpen) return;
|
|
2820
|
+
(settingsRef.current?.querySelector("[data-toolbar-settings-close]"))?.focus();
|
|
2821
|
+
}, [settingsOpen]);
|
|
2021
2822
|
/**
|
|
2022
2823
|
* B4: run one update check. Cache-aware (`loadCachedLatest` reuses an
|
|
2023
2824
|
* unexpired cached result without any network traffic) and never throws:
|
|
@@ -2112,6 +2913,64 @@ window.__ModuleLoader__.load({
|
|
|
2112
2913
|
window.addEventListener("hashchange", onHashChange);
|
|
2113
2914
|
return () => window.removeEventListener("hashchange", onHashChange);
|
|
2114
2915
|
}, []);
|
|
2916
|
+
(0, react.useLayoutEffect)(() => {
|
|
2917
|
+
if (marks.length < MIN_MARKS) {
|
|
2918
|
+
setRailBox(null);
|
|
2919
|
+
return;
|
|
2920
|
+
}
|
|
2921
|
+
const scrollport = document.querySelector("[data-conversation-scroll]");
|
|
2922
|
+
if (scrollport === null) return;
|
|
2923
|
+
const compute = () => {
|
|
2924
|
+
const sp = scrollport.getBoundingClientRect();
|
|
2925
|
+
setRailBox({
|
|
2926
|
+
top: sp.top,
|
|
2927
|
+
height: sp.height,
|
|
2928
|
+
right: Math.max(0, window.innerWidth - sp.right + inset),
|
|
2929
|
+
left: Math.max(0, sp.left + inset)
|
|
2930
|
+
});
|
|
2931
|
+
};
|
|
2932
|
+
compute();
|
|
2933
|
+
const observer = new ResizeObserver(compute);
|
|
2934
|
+
observer.observe(scrollport);
|
|
2935
|
+
window.addEventListener("resize", compute);
|
|
2936
|
+
return () => {
|
|
2937
|
+
observer.disconnect();
|
|
2938
|
+
window.removeEventListener("resize", compute);
|
|
2939
|
+
};
|
|
2940
|
+
}, [marks.length, inset]);
|
|
2941
|
+
(0, react.useLayoutEffect)(() => {
|
|
2942
|
+
setFocusIndex((f) => clampIndex(f, render.items.length));
|
|
2943
|
+
}, [render.items.length]);
|
|
2944
|
+
const lastBadge = (0, react.useMemo)(() => {
|
|
2945
|
+
if (displayMarks.length === 0) return null;
|
|
2946
|
+
const last = displayMarks[displayMarks.length - 1];
|
|
2947
|
+
return deriveBadge({
|
|
2948
|
+
nodeKinds: last.turn === void 0 ? NO_KINDS : kindsByTurn.get(last.turn) ?? NO_KINDS,
|
|
2949
|
+
lastMark: true,
|
|
2950
|
+
running,
|
|
2951
|
+
awaitingInput
|
|
2952
|
+
});
|
|
2953
|
+
}, [
|
|
2954
|
+
displayMarks,
|
|
2955
|
+
kindsByTurn,
|
|
2956
|
+
running,
|
|
2957
|
+
awaitingInput
|
|
2958
|
+
]);
|
|
2959
|
+
const pulseCss = (0, react.useMemo)(() => {
|
|
2960
|
+
if (lastBadge === null) return null;
|
|
2961
|
+
const style = badgeRingStyle(lastBadge);
|
|
2962
|
+
return style.pulse ? badgePulseCss(style.color) : null;
|
|
2963
|
+
}, [lastBadge]);
|
|
2964
|
+
(0, react.useEffect)(() => {
|
|
2965
|
+
if (!listOpen && !crossOpen) return;
|
|
2966
|
+
const onKey = (e) => {
|
|
2967
|
+
if (e.key !== "Escape") return;
|
|
2968
|
+
setListOpen(false);
|
|
2969
|
+
setCrossOpen(false);
|
|
2970
|
+
};
|
|
2971
|
+
window.addEventListener("keydown", onKey);
|
|
2972
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
2973
|
+
}, [listOpen, crossOpen]);
|
|
2115
2974
|
if (railBox === null || marks.length < MIN_MARKS) return null;
|
|
2116
2975
|
const updateQuery = (query) => {
|
|
2117
2976
|
setSearch({
|
|
@@ -2148,28 +3007,45 @@ window.__ModuleLoader__.load({
|
|
|
2148
3007
|
if (e.key === "Enter") advanceMatch();
|
|
2149
3008
|
if (e.key === "Escape") closeSearch();
|
|
2150
3009
|
};
|
|
2151
|
-
/**
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
/**
|
|
2160
|
-
const
|
|
2161
|
-
|
|
2162
|
-
|
|
3010
|
+
/**
|
|
3011
|
+
* B-design: viewport `right` offset for the floating layers. On the classic
|
|
3012
|
+
* right-side rail the panels sit left of the rail (their right edge at
|
|
3013
|
+
* railBox.right + hit + 8); on a LEFT rail every layer flips to the rail's
|
|
3014
|
+
* OTHER side — its left edge at railBox.left + hit + 8, which means its
|
|
3015
|
+
* viewport `right` must be backed out from the (known) panel width.
|
|
3016
|
+
*/
|
|
3017
|
+
const panelRightFor = (panelWidth) => side === "left" ? window.innerWidth - (railBox.left + hit + 8 + panelWidth) : railBox.right + hit + 8;
|
|
3018
|
+
/** Close the settings modal via its backdrop/close button paths. */
|
|
3019
|
+
const closeSettings = () => {
|
|
3020
|
+
setSettingsOpen(false);
|
|
3021
|
+
settingsBtnRef.current?.focus();
|
|
2163
3022
|
};
|
|
2164
3023
|
/** B1: a feature renders while the toolbar is EXPANDED or while it is pinned. */
|
|
2165
3024
|
const featureVisible = (id) => toolbarExpanded || pinned.includes(id);
|
|
3025
|
+
/** Base chrome-button style: accent-defined active tint, scaled hit area. */
|
|
3026
|
+
const chromeButtonStyle = (active) => ({
|
|
3027
|
+
width: hit,
|
|
3028
|
+
height: hit,
|
|
3029
|
+
flexShrink: 0,
|
|
3030
|
+
display: "flex",
|
|
3031
|
+
alignItems: "center",
|
|
3032
|
+
justifyContent: "center",
|
|
3033
|
+
background: active ? accentBg : "transparent",
|
|
3034
|
+
border: "none",
|
|
3035
|
+
padding: 0,
|
|
3036
|
+
cursor: "pointer",
|
|
3037
|
+
color: active ? accentSoft : "#8b96ab",
|
|
3038
|
+
transition: "background 120ms ease, color 120ms ease"
|
|
3039
|
+
});
|
|
2166
3040
|
/**
|
|
2167
3041
|
* B1: the data-driven feature registry. Each entry's render is the feature's
|
|
2168
|
-
*
|
|
2169
|
-
*
|
|
2170
|
-
*
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
3042
|
+
* rail-top chrome (data attributes / aria semantics preserved), moved
|
|
3043
|
+
* verbatim from the previous static button block; `search` is the whole
|
|
3044
|
+
* RailSearchUi (toggle + panel) so its lifecycle stays component-local in
|
|
3045
|
+
* the rail (search state lives in the rail and survives unmount). `settings`
|
|
3046
|
+
* lives LAST in the queue — the gear is a regular, default-unpinned feature;
|
|
3047
|
+
* the modal must stay reachable via 展开→齿轮.
|
|
3048
|
+
* Registry order = settings-menu order (站内搜索/全部提问/跨会话搜索/只看收藏/聚焦模式/检查更新/设置).
|
|
2173
3049
|
*
|
|
2174
3050
|
* EXTENSION POINT: push a new feature here (+ its id in toolbar-prefs.ts's
|
|
2175
3051
|
* TOOLBAR_PIN_IDS and its locale keys) and pinning/settings/expand all
|
|
@@ -2181,7 +3057,7 @@ window.__ModuleLoader__.load({
|
|
|
2181
3057
|
labelKey: "search.label",
|
|
2182
3058
|
render: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RailSearchUi, {
|
|
2183
3059
|
panelTop: railBox.top,
|
|
2184
|
-
panelRight:
|
|
3060
|
+
panelRight: panelRightFor(PANEL_WIDTH_SEARCH),
|
|
2185
3061
|
query: search.query,
|
|
2186
3062
|
panelOpen: search.panelOpen,
|
|
2187
3063
|
matches: matches.length,
|
|
@@ -2206,19 +3082,7 @@ window.__ModuleLoader__.load({
|
|
|
2206
3082
|
title: listOpen ? t("list.close") : t("list.open"),
|
|
2207
3083
|
"aria-pressed": listOpen,
|
|
2208
3084
|
onClick: () => setListOpen((v) => !v),
|
|
2209
|
-
style:
|
|
2210
|
-
width: DOT_HIT,
|
|
2211
|
-
height: DOT_HIT,
|
|
2212
|
-
flexShrink: 0,
|
|
2213
|
-
display: "flex",
|
|
2214
|
-
alignItems: "center",
|
|
2215
|
-
justifyContent: "center",
|
|
2216
|
-
background: listOpen ? "rgba(77, 124, 254, 0.18)" : "transparent",
|
|
2217
|
-
border: "none",
|
|
2218
|
-
padding: 0,
|
|
2219
|
-
cursor: "pointer",
|
|
2220
|
-
color: listOpen ? "#9db8ff" : "#8b96ab"
|
|
2221
|
-
},
|
|
3085
|
+
style: chromeButtonStyle(listOpen),
|
|
2222
3086
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2223
3087
|
width: "16",
|
|
2224
3088
|
height: "16",
|
|
@@ -2246,19 +3110,7 @@ window.__ModuleLoader__.load({
|
|
|
2246
3110
|
title: crossOpen ? t("search.cross.close") : t("search.cross.open"),
|
|
2247
3111
|
"aria-pressed": crossOpen,
|
|
2248
3112
|
onClick: () => setCrossOpen((v) => !v),
|
|
2249
|
-
style:
|
|
2250
|
-
width: DOT_HIT,
|
|
2251
|
-
height: DOT_HIT,
|
|
2252
|
-
flexShrink: 0,
|
|
2253
|
-
display: "flex",
|
|
2254
|
-
alignItems: "center",
|
|
2255
|
-
justifyContent: "center",
|
|
2256
|
-
background: crossOpen ? "rgba(77, 124, 254, 0.18)" : "transparent",
|
|
2257
|
-
border: "none",
|
|
2258
|
-
padding: 0,
|
|
2259
|
-
cursor: "pointer",
|
|
2260
|
-
color: crossOpen ? "#9db8ff" : "#8b96ab"
|
|
2261
|
-
},
|
|
3113
|
+
style: chromeButtonStyle(crossOpen),
|
|
2262
3114
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2263
3115
|
width: "16",
|
|
2264
3116
|
height: "16",
|
|
@@ -2293,19 +3145,7 @@ window.__ModuleLoader__.load({
|
|
|
2293
3145
|
"aria-pressed": bookmarksOnly,
|
|
2294
3146
|
"data-active": bookmarksOnly ? "true" : void 0,
|
|
2295
3147
|
onClick: () => setBookmarksOnly((v) => !v),
|
|
2296
|
-
style:
|
|
2297
|
-
width: DOT_HIT,
|
|
2298
|
-
height: DOT_HIT,
|
|
2299
|
-
flexShrink: 0,
|
|
2300
|
-
display: "flex",
|
|
2301
|
-
alignItems: "center",
|
|
2302
|
-
justifyContent: "center",
|
|
2303
|
-
background: bookmarksOnly ? "rgba(77, 124, 254, 0.18)" : "transparent",
|
|
2304
|
-
border: "none",
|
|
2305
|
-
padding: 0,
|
|
2306
|
-
cursor: "pointer",
|
|
2307
|
-
color: bookmarksOnly ? "#9db8ff" : "#8b96ab"
|
|
2308
|
-
},
|
|
3148
|
+
style: chromeButtonStyle(bookmarksOnly),
|
|
2309
3149
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2310
3150
|
width: "16",
|
|
2311
3151
|
height: "16",
|
|
@@ -2329,19 +3169,7 @@ window.__ModuleLoader__.load({
|
|
|
2329
3169
|
title: focusActive ? t("focus.off") : t("focus.on"),
|
|
2330
3170
|
"aria-pressed": focusActive,
|
|
2331
3171
|
onClick: () => setFocusActive((v) => !v),
|
|
2332
|
-
style:
|
|
2333
|
-
width: DOT_HIT,
|
|
2334
|
-
height: DOT_HIT,
|
|
2335
|
-
flexShrink: 0,
|
|
2336
|
-
display: "flex",
|
|
2337
|
-
alignItems: "center",
|
|
2338
|
-
justifyContent: "center",
|
|
2339
|
-
background: focusActive ? "rgba(126, 226, 168, 0.14)" : "transparent",
|
|
2340
|
-
border: "none",
|
|
2341
|
-
padding: 0,
|
|
2342
|
-
cursor: "pointer",
|
|
2343
|
-
color: focusActive ? "#7ee2a8" : "#8b96ab"
|
|
2344
|
-
},
|
|
3172
|
+
style: chromeButtonStyle(focusActive),
|
|
2345
3173
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2346
3174
|
width: "16",
|
|
2347
3175
|
height: "16",
|
|
@@ -2373,17 +3201,17 @@ window.__ModuleLoader__.load({
|
|
|
2373
3201
|
onClick: () => setUpdateOpen((v) => !v),
|
|
2374
3202
|
style: {
|
|
2375
3203
|
position: "relative",
|
|
2376
|
-
width:
|
|
2377
|
-
height:
|
|
3204
|
+
width: hit,
|
|
3205
|
+
height: hit,
|
|
2378
3206
|
flexShrink: 0,
|
|
2379
3207
|
display: "flex",
|
|
2380
3208
|
alignItems: "center",
|
|
2381
3209
|
justifyContent: "center",
|
|
2382
|
-
background:
|
|
3210
|
+
background: updateCheck.available ? "rgba(245, 197, 66, 0.14)" : updateOpen ? accentBg : "transparent",
|
|
2383
3211
|
border: "none",
|
|
2384
3212
|
padding: 0,
|
|
2385
3213
|
cursor: "pointer",
|
|
2386
|
-
color: updateCheck.available ? "#f5c542" : "#8b96ab"
|
|
3214
|
+
color: updateCheck.available ? "#f5c542" : updateOpen ? accentSoft : "#8b96ab"
|
|
2387
3215
|
},
|
|
2388
3216
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2389
3217
|
width: "16",
|
|
@@ -2416,6 +3244,40 @@ window.__ModuleLoader__.load({
|
|
|
2416
3244
|
}
|
|
2417
3245
|
})]
|
|
2418
3246
|
})
|
|
3247
|
+
},
|
|
3248
|
+
{
|
|
3249
|
+
id: "settings",
|
|
3250
|
+
labelKey: "settings.label",
|
|
3251
|
+
render: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3252
|
+
type: "button",
|
|
3253
|
+
ref: settingsBtnRef,
|
|
3254
|
+
"data-toolbar-settings": true,
|
|
3255
|
+
"aria-pressed": settingsOpen,
|
|
3256
|
+
"aria-label": settingsOpen ? t("toolbar.settings.close") : t("toolbar.settings.open"),
|
|
3257
|
+
title: settingsOpen ? t("toolbar.settings.close") : t("toolbar.settings.open"),
|
|
3258
|
+
onClick: () => setSettingsOpen((v) => !v),
|
|
3259
|
+
onMouseEnter: () => setSettingsHovered(true),
|
|
3260
|
+
onMouseLeave: () => setSettingsHovered(false),
|
|
3261
|
+
onFocus: () => setSettingsHovered(true),
|
|
3262
|
+
onBlur: () => setSettingsHovered(false),
|
|
3263
|
+
style: chromeButtonStyle(settingsOpen || settingsHovered),
|
|
3264
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
3265
|
+
width: "16",
|
|
3266
|
+
height: "16",
|
|
3267
|
+
viewBox: "0 0 24 24",
|
|
3268
|
+
fill: "none",
|
|
3269
|
+
stroke: "currentColor",
|
|
3270
|
+
strokeWidth: "2",
|
|
3271
|
+
strokeLinecap: "round",
|
|
3272
|
+
strokeLinejoin: "round",
|
|
3273
|
+
"aria-hidden": "true",
|
|
3274
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3275
|
+
cx: "12",
|
|
3276
|
+
cy: "12",
|
|
3277
|
+
r: "3"
|
|
3278
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })]
|
|
3279
|
+
})
|
|
3280
|
+
})
|
|
2419
3281
|
}
|
|
2420
3282
|
];
|
|
2421
3283
|
/** Focus the dot at `index` (no-op while the list is unmounted). */
|
|
@@ -2537,21 +3399,31 @@ window.__ModuleLoader__.load({
|
|
|
2537
3399
|
style: {
|
|
2538
3400
|
position: "fixed",
|
|
2539
3401
|
top: railBox.top,
|
|
2540
|
-
right: railBox.right,
|
|
3402
|
+
...side === "left" ? { left: railBox.left } : { right: railBox.right },
|
|
2541
3403
|
height: railBox.height,
|
|
2542
|
-
width:
|
|
3404
|
+
width: hit,
|
|
2543
3405
|
pointerEvents: "auto",
|
|
2544
3406
|
zIndex: 100,
|
|
2545
3407
|
display: "flex",
|
|
2546
3408
|
flexDirection: "column",
|
|
2547
3409
|
gap: 6,
|
|
2548
|
-
paddingTop: 6
|
|
3410
|
+
paddingTop: 6,
|
|
3411
|
+
"--ms-accent": accent,
|
|
3412
|
+
"--ms-accent-soft": accentSoft,
|
|
3413
|
+
"--ms-accent-bg": accentBg,
|
|
3414
|
+
"--ms-icon": `${iconSize}px`,
|
|
3415
|
+
"--ms-inset": `${inset}px`
|
|
2549
3416
|
},
|
|
2550
3417
|
"aria-label": t("rail.label"),
|
|
2551
3418
|
"data-focus-active": focusActive ? "true" : void 0,
|
|
3419
|
+
"data-accent": accent,
|
|
3420
|
+
"data-side": side,
|
|
3421
|
+
"data-icon-size": String(iconSize),
|
|
3422
|
+
"data-inset": String(inset),
|
|
2552
3423
|
children: [
|
|
2553
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children:
|
|
2554
|
-
focusActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children:
|
|
3424
|
+
pulseCss !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: pulseCss }),
|
|
3425
|
+
focusActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: buildFocusCss(prefs.focus) }),
|
|
3426
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: MODAL_CSS }),
|
|
2555
3427
|
showLoadOlder && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2556
3428
|
type: "button",
|
|
2557
3429
|
"data-load-older": true,
|
|
@@ -2563,8 +3435,8 @@ window.__ModuleLoader__.load({
|
|
|
2563
3435
|
loadOlder();
|
|
2564
3436
|
},
|
|
2565
3437
|
style: {
|
|
2566
|
-
width:
|
|
2567
|
-
height:
|
|
3438
|
+
width: hit,
|
|
3439
|
+
height: hit,
|
|
2568
3440
|
flexShrink: 0,
|
|
2569
3441
|
display: "flex",
|
|
2570
3442
|
alignItems: "center",
|
|
@@ -2587,19 +3459,11 @@ window.__ModuleLoader__.load({
|
|
|
2587
3459
|
"aria-label": toolbarExpanded ? t("toolbar.collapse") : t("toolbar.expand"),
|
|
2588
3460
|
title: toolbarExpanded ? t("toolbar.collapse") : t("toolbar.expand"),
|
|
2589
3461
|
onClick: () => setToolbarExpanded((v) => !v),
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
alignItems: "center",
|
|
2596
|
-
justifyContent: "center",
|
|
2597
|
-
background: "transparent",
|
|
2598
|
-
border: "none",
|
|
2599
|
-
padding: 0,
|
|
2600
|
-
cursor: "pointer",
|
|
2601
|
-
color: "#8b96ab"
|
|
2602
|
-
},
|
|
3462
|
+
onMouseEnter: () => setExpandHovered(true),
|
|
3463
|
+
onMouseLeave: () => setExpandHovered(false),
|
|
3464
|
+
onFocus: () => setExpandHovered(true),
|
|
3465
|
+
onBlur: () => setExpandHovered(false),
|
|
3466
|
+
style: chromeButtonStyle(expandHovered),
|
|
2603
3467
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2604
3468
|
width: "16",
|
|
2605
3469
|
height: "16",
|
|
@@ -2613,221 +3477,822 @@ window.__ModuleLoader__.load({
|
|
|
2613
3477
|
children: toolbarExpanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m18 15-6-6-6 6" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 9 6 6 6-6" })
|
|
2614
3478
|
})
|
|
2615
3479
|
}),
|
|
2616
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
"aria-expanded": settingsOpen,
|
|
2621
|
-
"aria-label": settingsOpen ? t("toolbar.settings.close") : t("toolbar.settings.open"),
|
|
2622
|
-
title: settingsOpen ? t("toolbar.settings.close") : t("toolbar.settings.open"),
|
|
2623
|
-
onClick: () => setSettingsOpen((v) => !v),
|
|
3480
|
+
toolbarFeatures.map((feature) => featureVisible(feature.id) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: feature.render() }, feature.id) : null),
|
|
3481
|
+
settingsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3482
|
+
"data-toolbar-settings-overlay": true,
|
|
3483
|
+
onClick: closeSettings,
|
|
2624
3484
|
style: {
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
3485
|
+
position: "fixed",
|
|
3486
|
+
inset: 0,
|
|
3487
|
+
background: "rgba(8, 10, 15, 0.55)",
|
|
3488
|
+
zIndex: 105,
|
|
2628
3489
|
display: "flex",
|
|
2629
3490
|
alignItems: "center",
|
|
2630
3491
|
justifyContent: "center",
|
|
2631
|
-
|
|
2632
|
-
border: "none",
|
|
2633
|
-
padding: 0,
|
|
2634
|
-
cursor: "pointer",
|
|
2635
|
-
color: settingsOpen ? "#9db8ff" : "#8b96ab"
|
|
3492
|
+
padding: 16
|
|
2636
3493
|
},
|
|
2637
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
style: {
|
|
2675
|
-
fontSize: 13,
|
|
2676
|
-
fontWeight: 600,
|
|
2677
|
-
color: "#e6e8ee",
|
|
2678
|
-
marginBottom: 2
|
|
2679
|
-
},
|
|
2680
|
-
children: t("settings.title")
|
|
2681
|
-
}),
|
|
2682
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2683
|
-
style: {
|
|
2684
|
-
fontSize: 12,
|
|
2685
|
-
color: "#8b96ab",
|
|
2686
|
-
marginBottom: 4,
|
|
2687
|
-
textAlign: "right"
|
|
2688
|
-
},
|
|
2689
|
-
children: t("settings.pin")
|
|
2690
|
-
}),
|
|
2691
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2692
|
-
role: "menu",
|
|
2693
|
-
"aria-label": t("settings.title"),
|
|
2694
|
-
style: {
|
|
2695
|
-
display: "flex",
|
|
2696
|
-
flexDirection: "column",
|
|
2697
|
-
gap: 2
|
|
2698
|
-
},
|
|
2699
|
-
children: toolbarFeatures.map((feature) => {
|
|
2700
|
-
const checked = pinned.includes(feature.id);
|
|
2701
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3494
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3495
|
+
ref: settingsRef,
|
|
3496
|
+
"data-toolbar-settings-panel": true,
|
|
3497
|
+
role: "dialog",
|
|
3498
|
+
"aria-modal": "true",
|
|
3499
|
+
"aria-label": t("settings.title"),
|
|
3500
|
+
onClick: (e) => e.stopPropagation(),
|
|
3501
|
+
style: {
|
|
3502
|
+
width: "min(600px, 92vw)",
|
|
3503
|
+
maxHeight: "78vh",
|
|
3504
|
+
overflowY: "auto",
|
|
3505
|
+
padding: 20,
|
|
3506
|
+
background: MODAL_BG,
|
|
3507
|
+
color: MODAL_FG,
|
|
3508
|
+
borderRadius: MODAL_RADIUS_PANEL,
|
|
3509
|
+
boxShadow: "0 24px 64px rgba(0, 0, 0, 0.55)",
|
|
3510
|
+
scrollbarWidth: "thin",
|
|
3511
|
+
scrollbarColor: "rgba(255, 255, 255, 0.2) transparent"
|
|
3512
|
+
},
|
|
3513
|
+
children: [
|
|
3514
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3515
|
+
style: {
|
|
3516
|
+
display: "flex",
|
|
3517
|
+
alignItems: "center",
|
|
3518
|
+
justifyContent: "space-between",
|
|
3519
|
+
gap: 8,
|
|
3520
|
+
marginBottom: 18
|
|
3521
|
+
},
|
|
3522
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3523
|
+
"data-toolbar-settings-title": true,
|
|
3524
|
+
style: {
|
|
3525
|
+
fontSize: 15,
|
|
3526
|
+
fontWeight: 600,
|
|
3527
|
+
color: MODAL_FG
|
|
3528
|
+
},
|
|
3529
|
+
children: t("settings.title")
|
|
3530
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2702
3531
|
type: "button",
|
|
2703
|
-
|
|
2704
|
-
"
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
onClick: () => onTogglePin(feature.id),
|
|
3532
|
+
"data-toolbar-settings-close": true,
|
|
3533
|
+
"aria-label": t("settings.close"),
|
|
3534
|
+
title: t("settings.close"),
|
|
3535
|
+
onClick: closeSettings,
|
|
2708
3536
|
style: {
|
|
3537
|
+
width: 28,
|
|
3538
|
+
height: 28,
|
|
3539
|
+
flexShrink: 0,
|
|
2709
3540
|
display: "flex",
|
|
2710
3541
|
alignItems: "center",
|
|
2711
|
-
justifyContent: "
|
|
2712
|
-
gap: 8,
|
|
2713
|
-
width: "100%",
|
|
2714
|
-
padding: "6px 8px",
|
|
2715
|
-
background: "transparent",
|
|
3542
|
+
justifyContent: "center",
|
|
2716
3543
|
border: "none",
|
|
2717
|
-
|
|
3544
|
+
padding: 0,
|
|
2718
3545
|
cursor: "pointer",
|
|
2719
|
-
color:
|
|
2720
|
-
|
|
2721
|
-
|
|
3546
|
+
color: MODAL_HINT,
|
|
3547
|
+
borderRadius: MODAL_RADIUS_CONTROL,
|
|
3548
|
+
lineHeight: 1
|
|
2722
3549
|
},
|
|
2723
|
-
children:
|
|
3550
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
3551
|
+
width: "14",
|
|
3552
|
+
height: "14",
|
|
3553
|
+
viewBox: "0 0 24 24",
|
|
3554
|
+
fill: "none",
|
|
3555
|
+
stroke: "currentColor",
|
|
3556
|
+
strokeWidth: "2",
|
|
3557
|
+
strokeLinecap: "round",
|
|
2724
3558
|
"aria-hidden": "true",
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
display: "flex",
|
|
2733
|
-
alignItems: "center",
|
|
2734
|
-
justifyContent: "center",
|
|
2735
|
-
color: "#ffffff",
|
|
2736
|
-
fontSize: 10,
|
|
2737
|
-
lineHeight: 1
|
|
2738
|
-
},
|
|
2739
|
-
children: checked ? "✓" : ""
|
|
2740
|
-
})]
|
|
2741
|
-
}, feature.id);
|
|
2742
|
-
})
|
|
2743
|
-
}),
|
|
2744
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2745
|
-
type: "button",
|
|
2746
|
-
"data-toolbar-settings-reset": true,
|
|
2747
|
-
onClick: onResetPins,
|
|
2748
|
-
style: {
|
|
2749
|
-
width: "100%",
|
|
2750
|
-
marginTop: 6,
|
|
2751
|
-
padding: "6px 8px",
|
|
2752
|
-
background: "transparent",
|
|
2753
|
-
border: "none",
|
|
2754
|
-
borderRadius: 6,
|
|
2755
|
-
cursor: "pointer",
|
|
2756
|
-
color: "#8b96ab",
|
|
2757
|
-
fontSize: 13,
|
|
2758
|
-
textAlign: "left"
|
|
2759
|
-
},
|
|
2760
|
-
children: t("settings.reset")
|
|
2761
|
-
}),
|
|
2762
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2763
|
-
"data-toolbar-settings-footer": true,
|
|
2764
|
-
style: {
|
|
2765
|
-
marginTop: 8,
|
|
2766
|
-
paddingTop: 8,
|
|
2767
|
-
borderTop: "1px solid rgba(255, 255, 255, 0.12)"
|
|
2768
|
-
},
|
|
2769
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2770
|
-
style: {
|
|
2771
|
-
fontSize: 12,
|
|
2772
|
-
color: "#8b96ab",
|
|
2773
|
-
marginBottom: 6
|
|
2774
|
-
},
|
|
2775
|
-
children: t("settings.support")
|
|
2776
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2777
|
-
style: {
|
|
2778
|
-
display: "flex",
|
|
2779
|
-
flexDirection: "column",
|
|
2780
|
-
gap: 4
|
|
2781
|
-
},
|
|
3559
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 6 6 18" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })]
|
|
3560
|
+
})
|
|
3561
|
+
})]
|
|
3562
|
+
}),
|
|
3563
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3564
|
+
"data-settings-section": true,
|
|
3565
|
+
style: { marginBottom: 20 },
|
|
2782
3566
|
children: [
|
|
2783
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2784
|
-
|
|
2785
|
-
target: "_blank",
|
|
2786
|
-
rel: "noreferrer",
|
|
2787
|
-
style: {
|
|
2788
|
-
fontSize: 12,
|
|
2789
|
-
color: "#9db8ff",
|
|
2790
|
-
textDecoration: "none"
|
|
2791
|
-
},
|
|
2792
|
-
children: t("settings.repo")
|
|
2793
|
-
}),
|
|
2794
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2795
|
-
href: "https://github.com/SnowCrescenter-tech/dsh-milestone",
|
|
2796
|
-
target: "_blank",
|
|
2797
|
-
rel: "noreferrer",
|
|
3567
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3568
|
+
"data-settings-section-title": true,
|
|
2798
3569
|
style: {
|
|
2799
|
-
fontSize:
|
|
2800
|
-
|
|
2801
|
-
|
|
3570
|
+
fontSize: 13,
|
|
3571
|
+
fontWeight: 600,
|
|
3572
|
+
color: MODAL_TITLE,
|
|
3573
|
+
marginBottom: 4
|
|
2802
3574
|
},
|
|
2803
|
-
children: t("settings.
|
|
3575
|
+
children: t("settings.section.features")
|
|
2804
3576
|
}),
|
|
2805
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2806
|
-
|
|
2807
|
-
target: "_blank",
|
|
2808
|
-
rel: "noreferrer",
|
|
3577
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3578
|
+
"data-settings-pin-hint": true,
|
|
2809
3579
|
style: {
|
|
2810
3580
|
fontSize: 12,
|
|
2811
|
-
color:
|
|
2812
|
-
|
|
3581
|
+
color: MODAL_HINT,
|
|
3582
|
+
lineHeight: 1.5,
|
|
3583
|
+
marginBottom: 10
|
|
2813
3584
|
},
|
|
2814
|
-
children: t("settings.
|
|
3585
|
+
children: t("settings.pin.hint")
|
|
2815
3586
|
}),
|
|
2816
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2817
|
-
href: "https://www.npmjs.com/package/dsh-milestone",
|
|
2818
|
-
target: "_blank",
|
|
2819
|
-
rel: "noreferrer",
|
|
3587
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2820
3588
|
style: {
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
3589
|
+
display: "flex",
|
|
3590
|
+
flexDirection: "column",
|
|
3591
|
+
gap: 2
|
|
2824
3592
|
},
|
|
2825
|
-
children:
|
|
3593
|
+
children: toolbarFeatures.map((feature) => {
|
|
3594
|
+
const checked = pinned.includes(feature.id);
|
|
3595
|
+
const active = descFeature === feature.id;
|
|
3596
|
+
const tipId = `ms-settings-tip-${feature.id}`;
|
|
3597
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3598
|
+
"data-toolbar-pin-row": true,
|
|
3599
|
+
"data-row-id": feature.id,
|
|
3600
|
+
style: { position: "relative" },
|
|
3601
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3602
|
+
type: "button",
|
|
3603
|
+
role: "switch",
|
|
3604
|
+
"data-toolbar-pin-toggle": true,
|
|
3605
|
+
"data-pin-id": feature.id,
|
|
3606
|
+
"aria-checked": checked,
|
|
3607
|
+
"aria-label": t(feature.labelKey),
|
|
3608
|
+
"aria-describedby": tipId,
|
|
3609
|
+
onMouseEnter: () => setDescFeature(feature.id),
|
|
3610
|
+
onMouseLeave: () => setDescFeature((prev) => prev === feature.id ? null : prev),
|
|
3611
|
+
onFocus: () => setDescFeature(feature.id),
|
|
3612
|
+
onBlur: () => setDescFeature((prev) => prev === feature.id ? null : prev),
|
|
3613
|
+
onClick: () => onTogglePin(feature.id),
|
|
3614
|
+
style: {
|
|
3615
|
+
display: "flex",
|
|
3616
|
+
alignItems: "center",
|
|
3617
|
+
justifyContent: "space-between",
|
|
3618
|
+
gap: 10,
|
|
3619
|
+
width: "100%",
|
|
3620
|
+
padding: "8px 10px",
|
|
3621
|
+
border: "none",
|
|
3622
|
+
borderRadius: MODAL_RADIUS_CONTROL,
|
|
3623
|
+
cursor: "pointer",
|
|
3624
|
+
color: MODAL_FG,
|
|
3625
|
+
fontSize: 13,
|
|
3626
|
+
textAlign: "left"
|
|
3627
|
+
},
|
|
3628
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3629
|
+
style: {
|
|
3630
|
+
overflow: "hidden",
|
|
3631
|
+
textOverflow: "ellipsis",
|
|
3632
|
+
whiteSpace: "nowrap"
|
|
3633
|
+
},
|
|
3634
|
+
children: t(feature.labelKey)
|
|
3635
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3636
|
+
"aria-hidden": "true",
|
|
3637
|
+
style: {
|
|
3638
|
+
position: "relative",
|
|
3639
|
+
width: 32,
|
|
3640
|
+
height: 18,
|
|
3641
|
+
flexShrink: 0,
|
|
3642
|
+
borderRadius: 9,
|
|
3643
|
+
background: checked ? accentStrong : "rgba(255, 255, 255, 0.16)",
|
|
3644
|
+
transition: "background 120ms ease"
|
|
3645
|
+
},
|
|
3646
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
3647
|
+
position: "absolute",
|
|
3648
|
+
top: 2,
|
|
3649
|
+
left: checked ? 16 : 2,
|
|
3650
|
+
width: 14,
|
|
3651
|
+
height: 14,
|
|
3652
|
+
borderRadius: "50%",
|
|
3653
|
+
background: "#ffffff",
|
|
3654
|
+
transition: "left 120ms ease"
|
|
3655
|
+
} })
|
|
3656
|
+
})]
|
|
3657
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3658
|
+
id: tipId,
|
|
3659
|
+
role: "tooltip",
|
|
3660
|
+
"data-settings-tip": true,
|
|
3661
|
+
"data-tip-for": feature.id,
|
|
3662
|
+
"data-tip-visible": active ? "true" : void 0,
|
|
3663
|
+
style: {
|
|
3664
|
+
position: "absolute",
|
|
3665
|
+
right: MODAL_TIP_RIGHT,
|
|
3666
|
+
top: "50%",
|
|
3667
|
+
maxWidth: "55%",
|
|
3668
|
+
transform: `translateY(-50%) translateX(${active ? 0 : 4}px)`,
|
|
3669
|
+
padding: "5px 10px",
|
|
3670
|
+
borderRadius: MODAL_RADIUS_CONTROL,
|
|
3671
|
+
background: MODAL_TIP_BG,
|
|
3672
|
+
border: `1px solid ${MODAL_BORDER}`,
|
|
3673
|
+
boxShadow: "0 8px 24px rgba(0, 0, 0, 0.35)",
|
|
3674
|
+
color: MODAL_TEXT,
|
|
3675
|
+
fontSize: 12,
|
|
3676
|
+
lineHeight: 1.45,
|
|
3677
|
+
opacity: active ? 1 : 0,
|
|
3678
|
+
visibility: active ? "visible" : "hidden",
|
|
3679
|
+
pointerEvents: "none",
|
|
3680
|
+
zIndex: 4
|
|
3681
|
+
},
|
|
3682
|
+
children: t(`settings.desc.${feature.id}`)
|
|
3683
|
+
})]
|
|
3684
|
+
}, feature.id);
|
|
3685
|
+
})
|
|
2826
3686
|
})
|
|
2827
3687
|
]
|
|
2828
|
-
})
|
|
2829
|
-
|
|
2830
|
-
|
|
3688
|
+
}),
|
|
3689
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3690
|
+
"data-settings-section": true,
|
|
3691
|
+
"data-settings-personal": true,
|
|
3692
|
+
style: { marginBottom: 20 },
|
|
3693
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3694
|
+
type: "button",
|
|
3695
|
+
"data-personal-toggle": true,
|
|
3696
|
+
"aria-expanded": personalOpen,
|
|
3697
|
+
"aria-label": personalOpen ? t("settings.personal.collapse") : t("settings.personal.expand"),
|
|
3698
|
+
title: personalOpen ? t("settings.personal.collapse") : t("settings.personal.expand"),
|
|
3699
|
+
onClick: () => setPersonalOpen((v) => !v),
|
|
3700
|
+
style: SECTION_TOGGLE_STYLE,
|
|
3701
|
+
children: [
|
|
3702
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3703
|
+
width: "14",
|
|
3704
|
+
height: "14",
|
|
3705
|
+
viewBox: "0 0 24 24",
|
|
3706
|
+
fill: "none",
|
|
3707
|
+
stroke: "currentColor",
|
|
3708
|
+
strokeWidth: "2.5",
|
|
3709
|
+
strokeLinecap: "round",
|
|
3710
|
+
strokeLinejoin: "round",
|
|
3711
|
+
"aria-hidden": "true",
|
|
3712
|
+
style: {
|
|
3713
|
+
flexShrink: 0,
|
|
3714
|
+
transform: personalOpen ? "rotate(90deg)" : "none"
|
|
3715
|
+
},
|
|
3716
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m9 18 6-6-6-6" })
|
|
3717
|
+
}),
|
|
3718
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3719
|
+
"data-settings-section-title": true,
|
|
3720
|
+
style: { flexShrink: 0 },
|
|
3721
|
+
children: t("settings.section.personal")
|
|
3722
|
+
}),
|
|
3723
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3724
|
+
"data-settings-personal-summary": true,
|
|
3725
|
+
style: SECTION_SUMMARY_STYLE,
|
|
3726
|
+
children: t("settings.personal.summary", {
|
|
3727
|
+
accent,
|
|
3728
|
+
icon: iconSize,
|
|
3729
|
+
side: side === "left" ? t("settings.side.left") : t("settings.side.right")
|
|
3730
|
+
})
|
|
3731
|
+
})
|
|
3732
|
+
]
|
|
3733
|
+
}), personalOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3734
|
+
"data-settings-personal-body": true,
|
|
3735
|
+
style: {
|
|
3736
|
+
padding: "10px 4px 8px",
|
|
3737
|
+
display: "flex",
|
|
3738
|
+
flexDirection: "column",
|
|
3739
|
+
gap: 12
|
|
3740
|
+
},
|
|
3741
|
+
children: [
|
|
3742
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3743
|
+
"data-settings-personal-hint": true,
|
|
3744
|
+
style: {
|
|
3745
|
+
fontSize: 12,
|
|
3746
|
+
color: MODAL_HINT,
|
|
3747
|
+
lineHeight: 1.5,
|
|
3748
|
+
padding: "0 6px"
|
|
3749
|
+
},
|
|
3750
|
+
children: t("settings.personal.hint")
|
|
3751
|
+
}),
|
|
3752
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3753
|
+
style: {
|
|
3754
|
+
display: "flex",
|
|
3755
|
+
alignItems: "center",
|
|
3756
|
+
gap: 10,
|
|
3757
|
+
flexWrap: "wrap"
|
|
3758
|
+
},
|
|
3759
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3760
|
+
style: {
|
|
3761
|
+
fontSize: 12.5,
|
|
3762
|
+
color: MODAL_HINT,
|
|
3763
|
+
width: 90,
|
|
3764
|
+
flexShrink: 0
|
|
3765
|
+
},
|
|
3766
|
+
children: t("settings.accent")
|
|
3767
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3768
|
+
"data-accent-swatches": true,
|
|
3769
|
+
style: {
|
|
3770
|
+
display: "flex",
|
|
3771
|
+
alignItems: "center",
|
|
3772
|
+
gap: 6,
|
|
3773
|
+
flexWrap: "wrap"
|
|
3774
|
+
},
|
|
3775
|
+
children: [ACCENT_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3776
|
+
type: "button",
|
|
3777
|
+
"data-accent-swatch": true,
|
|
3778
|
+
"data-accent": preset,
|
|
3779
|
+
"aria-label": preset,
|
|
3780
|
+
"aria-pressed": accent === preset,
|
|
3781
|
+
onClick: () => updatePrefs({ accent: preset }),
|
|
3782
|
+
style: {
|
|
3783
|
+
width: 22,
|
|
3784
|
+
height: 22,
|
|
3785
|
+
borderRadius: "50%",
|
|
3786
|
+
background: preset,
|
|
3787
|
+
border: accent === preset ? "2px solid #ffffff" : "2px solid rgba(255, 255, 255, 0.25)",
|
|
3788
|
+
boxShadow: accent === preset ? `0 0 0 2px ${preset}` : "none",
|
|
3789
|
+
padding: 0,
|
|
3790
|
+
cursor: "pointer",
|
|
3791
|
+
transition: "border-color 120ms ease, box-shadow 120ms ease"
|
|
3792
|
+
}
|
|
3793
|
+
}, preset)), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3794
|
+
"data-accent-custom": true,
|
|
3795
|
+
style: {
|
|
3796
|
+
display: "inline-flex",
|
|
3797
|
+
alignItems: "center",
|
|
3798
|
+
gap: 6,
|
|
3799
|
+
fontSize: 12.5,
|
|
3800
|
+
color: MODAL_TEXT,
|
|
3801
|
+
cursor: "pointer"
|
|
3802
|
+
},
|
|
3803
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3804
|
+
type: "color",
|
|
3805
|
+
value: accent,
|
|
3806
|
+
onChange: (e) => updatePrefs({ accent: e.target.value }),
|
|
3807
|
+
"aria-label": `${t("settings.custom")} ${t("settings.accent")}`,
|
|
3808
|
+
style: {
|
|
3809
|
+
width: 26,
|
|
3810
|
+
height: 26,
|
|
3811
|
+
padding: 0,
|
|
3812
|
+
border: "none",
|
|
3813
|
+
background: "transparent",
|
|
3814
|
+
cursor: "pointer"
|
|
3815
|
+
}
|
|
3816
|
+
}), t("settings.custom")]
|
|
3817
|
+
})]
|
|
3818
|
+
})]
|
|
3819
|
+
}),
|
|
3820
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3821
|
+
style: {
|
|
3822
|
+
display: "flex",
|
|
3823
|
+
alignItems: "center",
|
|
3824
|
+
gap: 10,
|
|
3825
|
+
flexWrap: "wrap"
|
|
3826
|
+
},
|
|
3827
|
+
children: [
|
|
3828
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3829
|
+
style: {
|
|
3830
|
+
fontSize: 12.5,
|
|
3831
|
+
color: MODAL_HINT,
|
|
3832
|
+
width: 90,
|
|
3833
|
+
flexShrink: 0
|
|
3834
|
+
},
|
|
3835
|
+
children: t("settings.iconSize")
|
|
3836
|
+
}),
|
|
3837
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3838
|
+
type: "range",
|
|
3839
|
+
"data-icon-size": true,
|
|
3840
|
+
min: 20,
|
|
3841
|
+
max: 36,
|
|
3842
|
+
step: 2,
|
|
3843
|
+
value: iconSize,
|
|
3844
|
+
onChange: (e) => updatePrefs({ iconSize: Number(e.target.value) }),
|
|
3845
|
+
style: {
|
|
3846
|
+
flex: 1,
|
|
3847
|
+
minWidth: 140,
|
|
3848
|
+
maxWidth: 260
|
|
3849
|
+
}
|
|
3850
|
+
}),
|
|
3851
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3852
|
+
"data-icon-size-value": true,
|
|
3853
|
+
style: {
|
|
3854
|
+
fontSize: 12.5,
|
|
3855
|
+
color: MODAL_TEXT,
|
|
3856
|
+
width: 40
|
|
3857
|
+
},
|
|
3858
|
+
children: [iconSize, "px"]
|
|
3859
|
+
})
|
|
3860
|
+
]
|
|
3861
|
+
}),
|
|
3862
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3863
|
+
style: {
|
|
3864
|
+
display: "flex",
|
|
3865
|
+
alignItems: "center",
|
|
3866
|
+
gap: 10,
|
|
3867
|
+
flexWrap: "wrap"
|
|
3868
|
+
},
|
|
3869
|
+
children: [
|
|
3870
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3871
|
+
style: {
|
|
3872
|
+
fontSize: 12.5,
|
|
3873
|
+
color: MODAL_HINT,
|
|
3874
|
+
width: 90,
|
|
3875
|
+
flexShrink: 0
|
|
3876
|
+
},
|
|
3877
|
+
children: t("settings.inset")
|
|
3878
|
+
}),
|
|
3879
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3880
|
+
type: "range",
|
|
3881
|
+
"data-inset": true,
|
|
3882
|
+
min: 0,
|
|
3883
|
+
max: 40,
|
|
3884
|
+
step: 2,
|
|
3885
|
+
value: inset,
|
|
3886
|
+
onChange: (e) => updatePrefs({ inset: Number(e.target.value) }),
|
|
3887
|
+
style: {
|
|
3888
|
+
flex: 1,
|
|
3889
|
+
minWidth: 140,
|
|
3890
|
+
maxWidth: 260
|
|
3891
|
+
}
|
|
3892
|
+
}),
|
|
3893
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3894
|
+
"data-inset-value": true,
|
|
3895
|
+
style: {
|
|
3896
|
+
fontSize: 12.5,
|
|
3897
|
+
color: MODAL_TEXT,
|
|
3898
|
+
width: 40
|
|
3899
|
+
},
|
|
3900
|
+
children: [inset, "px"]
|
|
3901
|
+
})
|
|
3902
|
+
]
|
|
3903
|
+
}),
|
|
3904
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3905
|
+
role: "radiogroup",
|
|
3906
|
+
"aria-label": t("settings.side"),
|
|
3907
|
+
style: {
|
|
3908
|
+
display: "flex",
|
|
3909
|
+
alignItems: "center",
|
|
3910
|
+
gap: 10,
|
|
3911
|
+
flexWrap: "wrap"
|
|
3912
|
+
},
|
|
3913
|
+
children: [
|
|
3914
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3915
|
+
style: {
|
|
3916
|
+
fontSize: 12.5,
|
|
3917
|
+
color: MODAL_HINT,
|
|
3918
|
+
width: 90,
|
|
3919
|
+
flexShrink: 0
|
|
3920
|
+
},
|
|
3921
|
+
children: t("settings.side")
|
|
3922
|
+
}),
|
|
3923
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3924
|
+
style: {
|
|
3925
|
+
display: "inline-flex",
|
|
3926
|
+
alignItems: "center",
|
|
3927
|
+
gap: 5,
|
|
3928
|
+
fontSize: 13,
|
|
3929
|
+
color: MODAL_FG,
|
|
3930
|
+
cursor: "pointer"
|
|
3931
|
+
},
|
|
3932
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3933
|
+
type: "radio",
|
|
3934
|
+
name: "ms-rail-side",
|
|
3935
|
+
"data-side-radio": true,
|
|
3936
|
+
value: "left",
|
|
3937
|
+
checked: side === "left",
|
|
3938
|
+
onChange: () => updatePrefs({ side: "left" })
|
|
3939
|
+
}), t("settings.side.left")]
|
|
3940
|
+
}),
|
|
3941
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3942
|
+
style: {
|
|
3943
|
+
display: "inline-flex",
|
|
3944
|
+
alignItems: "center",
|
|
3945
|
+
gap: 5,
|
|
3946
|
+
fontSize: 13,
|
|
3947
|
+
color: MODAL_FG,
|
|
3948
|
+
cursor: "pointer"
|
|
3949
|
+
},
|
|
3950
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3951
|
+
type: "radio",
|
|
3952
|
+
name: "ms-rail-side",
|
|
3953
|
+
"data-side-radio": true,
|
|
3954
|
+
value: "right",
|
|
3955
|
+
checked: side === "right",
|
|
3956
|
+
onChange: () => updatePrefs({ side: "right" })
|
|
3957
|
+
}), t("settings.side.right")]
|
|
3958
|
+
})
|
|
3959
|
+
]
|
|
3960
|
+
})
|
|
3961
|
+
]
|
|
3962
|
+
})]
|
|
3963
|
+
}),
|
|
3964
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3965
|
+
"data-settings-section": true,
|
|
3966
|
+
"data-focus-settings": true,
|
|
3967
|
+
style: { marginBottom: 20 },
|
|
3968
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3969
|
+
type: "button",
|
|
3970
|
+
"data-focus-toggle-settings": true,
|
|
3971
|
+
"aria-expanded": focusOpen,
|
|
3972
|
+
"aria-label": focusOpen ? t("settings.focus.collapse") : t("settings.focus.expand"),
|
|
3973
|
+
title: focusOpen ? t("settings.focus.collapse") : t("settings.focus.expand"),
|
|
3974
|
+
onClick: () => setFocusOpen((v) => !v),
|
|
3975
|
+
style: SECTION_TOGGLE_STYLE,
|
|
3976
|
+
children: [
|
|
3977
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3978
|
+
width: "14",
|
|
3979
|
+
height: "14",
|
|
3980
|
+
viewBox: "0 0 24 24",
|
|
3981
|
+
fill: "none",
|
|
3982
|
+
stroke: "currentColor",
|
|
3983
|
+
strokeWidth: "2.5",
|
|
3984
|
+
strokeLinecap: "round",
|
|
3985
|
+
strokeLinejoin: "round",
|
|
3986
|
+
"aria-hidden": "true",
|
|
3987
|
+
style: {
|
|
3988
|
+
flexShrink: 0,
|
|
3989
|
+
transform: focusOpen ? "rotate(90deg)" : "none"
|
|
3990
|
+
},
|
|
3991
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m9 18 6-6-6-6" })
|
|
3992
|
+
}),
|
|
3993
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3994
|
+
"data-settings-section-title": true,
|
|
3995
|
+
style: { flexShrink: 0 },
|
|
3996
|
+
children: t("settings.section.focus")
|
|
3997
|
+
}),
|
|
3998
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3999
|
+
"data-focus-summary": true,
|
|
4000
|
+
style: SECTION_SUMMARY_STYLE,
|
|
4001
|
+
children: focusSummary
|
|
4002
|
+
})
|
|
4003
|
+
]
|
|
4004
|
+
}), focusOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4005
|
+
"data-settings-focus-body": true,
|
|
4006
|
+
style: {
|
|
4007
|
+
padding: "10px 4px 8px",
|
|
4008
|
+
display: "flex",
|
|
4009
|
+
flexDirection: "column",
|
|
4010
|
+
gap: 12
|
|
4011
|
+
},
|
|
4012
|
+
children: [
|
|
4013
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4014
|
+
"data-settings-focus-hint": true,
|
|
4015
|
+
style: {
|
|
4016
|
+
fontSize: 12,
|
|
4017
|
+
color: MODAL_HINT,
|
|
4018
|
+
lineHeight: 1.5,
|
|
4019
|
+
padding: "0 6px"
|
|
4020
|
+
},
|
|
4021
|
+
children: t("settings.focus.hint")
|
|
4022
|
+
}),
|
|
4023
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4024
|
+
"data-focus-option": true,
|
|
4025
|
+
style: {
|
|
4026
|
+
display: "flex",
|
|
4027
|
+
alignItems: "center",
|
|
4028
|
+
gap: 8,
|
|
4029
|
+
padding: "6px 8px",
|
|
4030
|
+
borderRadius: MODAL_RADIUS_CONTROL,
|
|
4031
|
+
fontSize: 13,
|
|
4032
|
+
color: MODAL_FG,
|
|
4033
|
+
cursor: "pointer"
|
|
4034
|
+
},
|
|
4035
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4036
|
+
type: "checkbox",
|
|
4037
|
+
"data-focus-dim-think": true,
|
|
4038
|
+
checked: prefs.focus.dimThink,
|
|
4039
|
+
onChange: (e) => updateFocus({ dimThink: e.target.checked })
|
|
4040
|
+
}), t("settings.focus.dimThink")]
|
|
4041
|
+
}),
|
|
4042
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4043
|
+
"data-focus-option": true,
|
|
4044
|
+
style: {
|
|
4045
|
+
display: "flex",
|
|
4046
|
+
alignItems: "center",
|
|
4047
|
+
gap: 8,
|
|
4048
|
+
padding: "6px 8px",
|
|
4049
|
+
borderRadius: MODAL_RADIUS_CONTROL,
|
|
4050
|
+
fontSize: 13,
|
|
4051
|
+
color: MODAL_FG,
|
|
4052
|
+
cursor: "pointer"
|
|
4053
|
+
},
|
|
4054
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4055
|
+
type: "checkbox",
|
|
4056
|
+
"data-focus-dim-tools": true,
|
|
4057
|
+
checked: prefs.focus.dimTools,
|
|
4058
|
+
onChange: (e) => updateFocus({ dimTools: e.target.checked })
|
|
4059
|
+
}), t("settings.focus.dimTools")]
|
|
4060
|
+
}),
|
|
4061
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4062
|
+
"data-focus-option": true,
|
|
4063
|
+
style: {
|
|
4064
|
+
display: "flex",
|
|
4065
|
+
alignItems: "center",
|
|
4066
|
+
gap: 8,
|
|
4067
|
+
padding: "6px 8px",
|
|
4068
|
+
borderRadius: MODAL_RADIUS_CONTROL,
|
|
4069
|
+
fontSize: 13,
|
|
4070
|
+
color: MODAL_FG,
|
|
4071
|
+
cursor: "pointer"
|
|
4072
|
+
},
|
|
4073
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4074
|
+
type: "checkbox",
|
|
4075
|
+
"data-focus-collapse-think": true,
|
|
4076
|
+
checked: prefs.focus.collapseThink,
|
|
4077
|
+
onChange: (e) => updateFocus({ collapseThink: e.target.checked })
|
|
4078
|
+
}), t("settings.focus.collapseThink")]
|
|
4079
|
+
}),
|
|
4080
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4081
|
+
style: {
|
|
4082
|
+
display: "flex",
|
|
4083
|
+
alignItems: "center",
|
|
4084
|
+
gap: 10,
|
|
4085
|
+
flexWrap: "wrap"
|
|
4086
|
+
},
|
|
4087
|
+
children: [
|
|
4088
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4089
|
+
style: {
|
|
4090
|
+
fontSize: 12.5,
|
|
4091
|
+
color: MODAL_HINT,
|
|
4092
|
+
width: 90,
|
|
4093
|
+
flexShrink: 0
|
|
4094
|
+
},
|
|
4095
|
+
children: t("settings.focus.opacity")
|
|
4096
|
+
}),
|
|
4097
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4098
|
+
type: "range",
|
|
4099
|
+
"data-focus-opacity": true,
|
|
4100
|
+
min: .2,
|
|
4101
|
+
max: .8,
|
|
4102
|
+
step: .1,
|
|
4103
|
+
value: prefs.focus.opacity,
|
|
4104
|
+
onChange: (e) => updateFocus({ opacity: Number(e.target.value) }),
|
|
4105
|
+
style: {
|
|
4106
|
+
flex: 1,
|
|
4107
|
+
minWidth: 140,
|
|
4108
|
+
maxWidth: 260
|
|
4109
|
+
}
|
|
4110
|
+
}),
|
|
4111
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4112
|
+
"data-focus-opacity-value": true,
|
|
4113
|
+
style: {
|
|
4114
|
+
fontSize: 12.5,
|
|
4115
|
+
color: MODAL_TEXT,
|
|
4116
|
+
width: 44
|
|
4117
|
+
},
|
|
4118
|
+
children: [Math.round(prefs.focus.opacity * 100), "%"]
|
|
4119
|
+
})
|
|
4120
|
+
]
|
|
4121
|
+
})
|
|
4122
|
+
]
|
|
4123
|
+
})]
|
|
4124
|
+
}),
|
|
4125
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4126
|
+
"data-settings-section": true,
|
|
4127
|
+
"data-settings-lang": true,
|
|
4128
|
+
style: { marginBottom: 20 },
|
|
4129
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4130
|
+
"data-settings-section-title": true,
|
|
4131
|
+
style: {
|
|
4132
|
+
fontSize: 13,
|
|
4133
|
+
fontWeight: 600,
|
|
4134
|
+
color: MODAL_TITLE,
|
|
4135
|
+
marginBottom: 8
|
|
4136
|
+
},
|
|
4137
|
+
children: t("settings.language")
|
|
4138
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4139
|
+
role: "radiogroup",
|
|
4140
|
+
"aria-label": t("settings.language"),
|
|
4141
|
+
style: {
|
|
4142
|
+
display: "flex",
|
|
4143
|
+
alignItems: "center",
|
|
4144
|
+
gap: 18,
|
|
4145
|
+
flexWrap: "wrap"
|
|
4146
|
+
},
|
|
4147
|
+
children: [
|
|
4148
|
+
"system",
|
|
4149
|
+
"zh",
|
|
4150
|
+
"en"
|
|
4151
|
+
].map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
4152
|
+
style: {
|
|
4153
|
+
display: "inline-flex",
|
|
4154
|
+
alignItems: "center",
|
|
4155
|
+
gap: 5,
|
|
4156
|
+
fontSize: 13,
|
|
4157
|
+
color: MODAL_FG,
|
|
4158
|
+
cursor: "pointer"
|
|
4159
|
+
},
|
|
4160
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4161
|
+
type: "radio",
|
|
4162
|
+
name: "ms-rail-locale",
|
|
4163
|
+
"data-locale-pref": true,
|
|
4164
|
+
value,
|
|
4165
|
+
checked: prefs.locale === value,
|
|
4166
|
+
onChange: () => updatePrefs({ locale: value })
|
|
4167
|
+
}), t(`settings.lang.${value}`)]
|
|
4168
|
+
}, value))
|
|
4169
|
+
})]
|
|
4170
|
+
}),
|
|
4171
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4172
|
+
"data-toolbar-settings-footer": true,
|
|
4173
|
+
style: { textAlign: "center" },
|
|
4174
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4175
|
+
style: {
|
|
4176
|
+
fontSize: 12,
|
|
4177
|
+
color: MODAL_HINT,
|
|
4178
|
+
marginBottom: 10
|
|
4179
|
+
},
|
|
4180
|
+
children: t("settings.support")
|
|
4181
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4182
|
+
"data-support-grid": true,
|
|
4183
|
+
style: {
|
|
4184
|
+
display: "grid",
|
|
4185
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
4186
|
+
gap: 10,
|
|
4187
|
+
maxWidth: 460,
|
|
4188
|
+
margin: "0 auto"
|
|
4189
|
+
},
|
|
4190
|
+
children: [
|
|
4191
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
4192
|
+
href: "https://github.com/SnowCrescenter-tech/dsh-milestone",
|
|
4193
|
+
target: "_blank",
|
|
4194
|
+
rel: "noreferrer",
|
|
4195
|
+
"data-support-card": true,
|
|
4196
|
+
"data-card": "repo",
|
|
4197
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4198
|
+
width: "14",
|
|
4199
|
+
height: "14",
|
|
4200
|
+
viewBox: "0 0 24 24",
|
|
4201
|
+
fill: "none",
|
|
4202
|
+
stroke: "currentColor",
|
|
4203
|
+
strokeWidth: "2",
|
|
4204
|
+
strokeLinecap: "round",
|
|
4205
|
+
strokeLinejoin: "round",
|
|
4206
|
+
"aria-hidden": "true",
|
|
4207
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" })
|
|
4208
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.repo") })]
|
|
4209
|
+
}),
|
|
4210
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
4211
|
+
href: "https://github.com/SnowCrescenter-tech/dsh-milestone",
|
|
4212
|
+
target: "_blank",
|
|
4213
|
+
rel: "noreferrer",
|
|
4214
|
+
"data-support-card": true,
|
|
4215
|
+
"data-card": "star",
|
|
4216
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4217
|
+
width: "14",
|
|
4218
|
+
height: "14",
|
|
4219
|
+
viewBox: "0 0 24 24",
|
|
4220
|
+
fill: "currentColor",
|
|
4221
|
+
stroke: "none",
|
|
4222
|
+
"aria-hidden": "true",
|
|
4223
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" })
|
|
4224
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.star") })]
|
|
4225
|
+
}),
|
|
4226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
4227
|
+
href: `https://github.com/SnowCrescenter-tech/dsh-milestone/issues`,
|
|
4228
|
+
target: "_blank",
|
|
4229
|
+
rel: "noreferrer",
|
|
4230
|
+
"data-support-card": true,
|
|
4231
|
+
"data-card": "issues",
|
|
4232
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
4233
|
+
width: "14",
|
|
4234
|
+
height: "14",
|
|
4235
|
+
viewBox: "0 0 24 24",
|
|
4236
|
+
fill: "none",
|
|
4237
|
+
stroke: "currentColor",
|
|
4238
|
+
strokeWidth: "2",
|
|
4239
|
+
"aria-hidden": "true",
|
|
4240
|
+
children: [
|
|
4241
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4242
|
+
cx: "12",
|
|
4243
|
+
cy: "12",
|
|
4244
|
+
r: "9"
|
|
4245
|
+
}),
|
|
4246
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
4247
|
+
d: "M12 8v4",
|
|
4248
|
+
strokeLinecap: "round"
|
|
4249
|
+
}),
|
|
4250
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
4251
|
+
cx: "12",
|
|
4252
|
+
cy: "16",
|
|
4253
|
+
r: "0.5",
|
|
4254
|
+
fill: "currentColor"
|
|
4255
|
+
})
|
|
4256
|
+
]
|
|
4257
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.issues") })]
|
|
4258
|
+
}),
|
|
4259
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
4260
|
+
href: "https://www.npmjs.com/package/dsh-milestone",
|
|
4261
|
+
target: "_blank",
|
|
4262
|
+
rel: "noreferrer",
|
|
4263
|
+
"data-support-card": true,
|
|
4264
|
+
"data-card": "npm",
|
|
4265
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4266
|
+
width: "14",
|
|
4267
|
+
height: "14",
|
|
4268
|
+
viewBox: "0 0 24 24",
|
|
4269
|
+
fill: "currentColor",
|
|
4270
|
+
stroke: "none",
|
|
4271
|
+
"aria-hidden": "true",
|
|
4272
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 8.5h20V15h-6v2.5h-3V15H2V8.5zm1.5 1.5v3.5H6V11.5h1.5v3.5h1.5V10h-4.5zm6 0v5h3V13h2v2h1.5v-5h-6.5z" })
|
|
4273
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.npm") })]
|
|
4274
|
+
})
|
|
4275
|
+
]
|
|
4276
|
+
})]
|
|
4277
|
+
}),
|
|
4278
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4279
|
+
type: "button",
|
|
4280
|
+
"data-toolbar-settings-reset": true,
|
|
4281
|
+
onClick: onResetAll,
|
|
4282
|
+
style: {
|
|
4283
|
+
display: "block",
|
|
4284
|
+
margin: "16px auto 2px",
|
|
4285
|
+
padding: "7px 16px",
|
|
4286
|
+
border: `1px solid ${MODAL_BORDER}`,
|
|
4287
|
+
borderRadius: MODAL_RADIUS_CONTROL,
|
|
4288
|
+
cursor: "pointer",
|
|
4289
|
+
color: MODAL_TEXT,
|
|
4290
|
+
fontSize: 12.5
|
|
4291
|
+
},
|
|
4292
|
+
children: t("settings.reset")
|
|
4293
|
+
})
|
|
4294
|
+
]
|
|
4295
|
+
})
|
|
2831
4296
|
}),
|
|
2832
4297
|
updateOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2833
4298
|
ref: updatePanelRef,
|
|
@@ -2835,7 +4300,7 @@ window.__ModuleLoader__.load({
|
|
|
2835
4300
|
style: {
|
|
2836
4301
|
position: "fixed",
|
|
2837
4302
|
top: railBox.top,
|
|
2838
|
-
right:
|
|
4303
|
+
right: panelRightFor(PANEL_WIDTH_STANDARD),
|
|
2839
4304
|
width: "min(280px, calc(100vw - 48px))",
|
|
2840
4305
|
padding: "10px 12px",
|
|
2841
4306
|
background: "rgba(20, 24, 32, 0.97)",
|
|
@@ -2865,7 +4330,7 @@ window.__ModuleLoader__.load({
|
|
|
2865
4330
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2866
4331
|
style: { color: "#8b96ab" },
|
|
2867
4332
|
children: [t("update.current"), ": "]
|
|
2868
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "0.6.
|
|
4333
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "0.6.3" })] }),
|
|
2869
4334
|
updateCheck.phase === "ok" && updateCheck.latest !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2870
4335
|
"data-update-latest": true,
|
|
2871
4336
|
children: [
|
|
@@ -2908,7 +4373,7 @@ window.__ModuleLoader__.load({
|
|
|
2908
4373
|
border: "none",
|
|
2909
4374
|
padding: 0,
|
|
2910
4375
|
cursor: "pointer",
|
|
2911
|
-
color:
|
|
4376
|
+
color: accentSoft,
|
|
2912
4377
|
fontSize: 12,
|
|
2913
4378
|
textDecoration: "underline"
|
|
2914
4379
|
},
|
|
@@ -2928,7 +4393,7 @@ window.__ModuleLoader__.load({
|
|
|
2928
4393
|
target: "_blank",
|
|
2929
4394
|
rel: "noreferrer",
|
|
2930
4395
|
style: {
|
|
2931
|
-
color:
|
|
4396
|
+
color: accentSoft,
|
|
2932
4397
|
textDecoration: "none"
|
|
2933
4398
|
},
|
|
2934
4399
|
children: t("update.goNpm")
|
|
@@ -2952,11 +4417,11 @@ window.__ModuleLoader__.load({
|
|
|
2952
4417
|
style: {
|
|
2953
4418
|
marginTop: 4,
|
|
2954
4419
|
padding: "6px 10px",
|
|
2955
|
-
background: updateCheck.phase === "checking" ? "transparent" :
|
|
4420
|
+
background: updateCheck.phase === "checking" ? "transparent" : accentBg,
|
|
2956
4421
|
border: "none",
|
|
2957
4422
|
borderRadius: 6,
|
|
2958
4423
|
cursor: updateCheck.phase === "checking" ? "default" : "pointer",
|
|
2959
|
-
color: updateCheck.phase === "checking" ? "#5a6375" :
|
|
4424
|
+
color: updateCheck.phase === "checking" ? "#5a6375" : accentSoft,
|
|
2960
4425
|
fontSize: 12,
|
|
2961
4426
|
alignSelf: "flex-start"
|
|
2962
4427
|
},
|
|
@@ -2967,14 +4432,14 @@ window.__ModuleLoader__.load({
|
|
|
2967
4432
|
}),
|
|
2968
4433
|
listOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MilestoneListPanel, {
|
|
2969
4434
|
panelTop: railBox.top,
|
|
2970
|
-
panelRight:
|
|
4435
|
+
panelRight: panelRightFor(PANEL_WIDTH_STANDARD),
|
|
2971
4436
|
marks,
|
|
2972
4437
|
onJump: jump,
|
|
2973
4438
|
t
|
|
2974
4439
|
}),
|
|
2975
4440
|
crossOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MilestoneSessionSearch, {
|
|
2976
4441
|
panelTop: railBox.top,
|
|
2977
|
-
panelRight:
|
|
4442
|
+
panelRight: panelRightFor(PANEL_WIDTH_STANDARD),
|
|
2978
4443
|
onClose: () => setCrossOpen(false),
|
|
2979
4444
|
searchSessions,
|
|
2980
4445
|
openSession,
|
|
@@ -2994,12 +4459,12 @@ window.__ModuleLoader__.load({
|
|
|
2994
4459
|
display: "flex",
|
|
2995
4460
|
flexDirection: "column",
|
|
2996
4461
|
alignItems: "center",
|
|
2997
|
-
gap
|
|
4462
|
+
gap,
|
|
2998
4463
|
padding: "6px 0",
|
|
2999
4464
|
scrollbarWidth: "none"
|
|
3000
4465
|
},
|
|
3001
4466
|
children: render.items.map((item, i) => {
|
|
3002
|
-
const
|
|
4467
|
+
const showGroupGap = separatorIndices.has(i) && i > 0;
|
|
3003
4468
|
const mark = displayMarks[item.displayIndex];
|
|
3004
4469
|
const summaryCount = collapsedSummaries.get(mark.key);
|
|
3005
4470
|
const bookmarked = isBookmarked(bookmarkedKeys, mark.key);
|
|
@@ -3011,7 +4476,7 @@ window.__ModuleLoader__.load({
|
|
|
3011
4476
|
isCurrent: !hasQuery && mark.key === currentKey
|
|
3012
4477
|
});
|
|
3013
4478
|
const isHovered = hover?.mark.key === mark.key;
|
|
3014
|
-
const boxShadow = isHovered ?
|
|
4479
|
+
const boxShadow = isHovered ? `0 0 0 3px ${rgbaString(accent, .35) ?? "rgba(77, 124, 254, 0.35)"}` : dotState === "active" ? `0 0 0 3px rgba(255, 255, 255, 0.9), 0 0 10px 2px ${rgbaString(accent, .55) ?? "rgba(77, 124, 254, 0.55)"}` : dotState === "current" ? "0 0 0 3px rgba(255, 255, 255, 0.75)" : dotState === "match" ? `0 0 0 2px ${rgbaString(accent, .45) ?? "rgba(77, 124, 254, 0.45)"}` : "none";
|
|
3015
4480
|
const badge = deriveBadge({
|
|
3016
4481
|
nodeKinds: mark.turn === void 0 ? NO_KINDS : kindsByTurn.get(mark.turn) ?? NO_KINDS,
|
|
3017
4482
|
lastMark: item.displayIndex === displayMarks.length - 1,
|
|
@@ -3019,21 +4484,11 @@ window.__ModuleLoader__.load({
|
|
|
3019
4484
|
awaitingInput
|
|
3020
4485
|
});
|
|
3021
4486
|
const ringStyle = badge === null ? null : badgeRingStyle(badge);
|
|
3022
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
3023
|
-
"data-turn-separator": true,
|
|
3024
|
-
"data-turn": mark.turn === void 0 ? void 0 : mark.turn,
|
|
3025
|
-
style: {
|
|
3026
|
-
width: DOT_HIT - 8,
|
|
3027
|
-
height: 1,
|
|
3028
|
-
flexShrink: 0,
|
|
3029
|
-
background: "rgba(139, 150, 171, 0.35)",
|
|
3030
|
-
borderRadius: 1
|
|
3031
|
-
}
|
|
3032
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4487
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3033
4488
|
type: "button",
|
|
3034
4489
|
style: {
|
|
3035
|
-
width:
|
|
3036
|
-
height:
|
|
4490
|
+
width: hit,
|
|
4491
|
+
height: hit,
|
|
3037
4492
|
flexShrink: 0,
|
|
3038
4493
|
display: "flex",
|
|
3039
4494
|
alignItems: "center",
|
|
@@ -3041,7 +4496,8 @@ window.__ModuleLoader__.load({
|
|
|
3041
4496
|
background: "transparent",
|
|
3042
4497
|
border: "none",
|
|
3043
4498
|
padding: 0,
|
|
3044
|
-
cursor: "pointer"
|
|
4499
|
+
cursor: "pointer",
|
|
4500
|
+
marginTop: showGroupGap ? GROUP_GAP_EXTRA : 0
|
|
3045
4501
|
},
|
|
3046
4502
|
onMouseEnter: (e) => {
|
|
3047
4503
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
@@ -3052,6 +4508,8 @@ window.__ModuleLoader__.load({
|
|
|
3052
4508
|
},
|
|
3053
4509
|
onClick: () => jump(mark.key),
|
|
3054
4510
|
"data-rail-dot": true,
|
|
4511
|
+
"data-turn-gap": showGroupGap ? "true" : void 0,
|
|
4512
|
+
"data-turn": showGroupGap && mark.turn !== void 0 ? mark.turn : void 0,
|
|
3055
4513
|
"data-collapsed-summary": summaryCount !== void 0 ? "true" : void 0,
|
|
3056
4514
|
"data-collapsed-count": summaryCount,
|
|
3057
4515
|
tabIndex: focusIndex === i ? 0 : -1,
|
|
@@ -3063,10 +4521,10 @@ window.__ModuleLoader__.load({
|
|
|
3063
4521
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3064
4522
|
style: {
|
|
3065
4523
|
position: "relative",
|
|
3066
|
-
width:
|
|
3067
|
-
height:
|
|
4524
|
+
width: size,
|
|
4525
|
+
height: size,
|
|
3068
4526
|
borderRadius: "50%",
|
|
3069
|
-
background: dotColor(item.displayIndex, marks.length),
|
|
4527
|
+
background: dotColor(item.displayIndex, marks.length, accent),
|
|
3070
4528
|
boxShadow,
|
|
3071
4529
|
transition: "transform 120ms ease, opacity 120ms ease",
|
|
3072
4530
|
transform: `scale(${isHovered ? 1.35 : dotState === "active" || dotState === "current" ? 1.25 : 1})`,
|
|
@@ -3079,18 +4537,18 @@ window.__ModuleLoader__.load({
|
|
|
3079
4537
|
position: "absolute",
|
|
3080
4538
|
inset: -3,
|
|
3081
4539
|
borderRadius: "50%",
|
|
3082
|
-
|
|
4540
|
+
boxShadow: ringStyle.shadow,
|
|
3083
4541
|
color: ringStyle.color,
|
|
3084
4542
|
pointerEvents: "none",
|
|
3085
|
-
animation: ringStyle.pulse ? "milestone-badge-pulse
|
|
4543
|
+
animation: ringStyle.pulse ? "milestone-badge-pulse 2s ease-in-out infinite" : void 0
|
|
3086
4544
|
}
|
|
3087
4545
|
})
|
|
3088
4546
|
})
|
|
3089
|
-
})
|
|
4547
|
+
}) }, mark.key);
|
|
3090
4548
|
})
|
|
3091
4549
|
}),
|
|
3092
4550
|
hover !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MilestoneRailTooltip, {
|
|
3093
|
-
panelRight:
|
|
4551
|
+
panelRight: panelRightFor(TOOLTIP_ANCHOR_WIDTH),
|
|
3094
4552
|
hover,
|
|
3095
4553
|
bookmarked: isBookmarked(bookmarkedKeys, hover.mark.key),
|
|
3096
4554
|
onToggleBookmark: () => onToggleBookmark(hover.mark.key),
|
|
@@ -3109,8 +4567,13 @@ window.__ModuleLoader__.load({
|
|
|
3109
4567
|
style: {
|
|
3110
4568
|
position: "absolute",
|
|
3111
4569
|
bottom: 6,
|
|
3112
|
-
|
|
3113
|
-
|
|
4570
|
+
...side === "left" ? {
|
|
4571
|
+
left: "100%",
|
|
4572
|
+
marginLeft: 8
|
|
4573
|
+
} : {
|
|
4574
|
+
right: "100%",
|
|
4575
|
+
marginRight: 8
|
|
4576
|
+
},
|
|
3114
4577
|
whiteSpace: "nowrap",
|
|
3115
4578
|
fontSize: 12,
|
|
3116
4579
|
lineHeight: 1,
|
|
@@ -3233,213 +4696,6 @@ window.__ModuleLoader__.load({
|
|
|
3233
4696
|
});
|
|
3234
4697
|
}
|
|
3235
4698
|
//#endregion
|
|
3236
|
-
//#region src/client/locales.ts
|
|
3237
|
-
/**
|
|
3238
|
-
* UI strings for the milestone rail, keyed flat (single-language-per-key,
|
|
3239
|
-
* no nesting) so the later i18n threading stays a mechanical
|
|
3240
|
-
* `value.replace('{name}', n)` substitution.
|
|
3241
|
-
*
|
|
3242
|
-
* `zh` is the source of truth and the key registry: it byte-matches the
|
|
3243
|
-
* current hardcoded output of MilestoneRail / MilestoneRailTooltip /
|
|
3244
|
-
* MilestoneRailSearch exactly (each `{n}`/`{m}`/`{name}` slot stands in for
|
|
3245
|
-
* the interpolated number or label), so swapping in these templates is
|
|
3246
|
-
* behavior-preserving. `en` is typed `Record<MilestoneKey, string>` so a
|
|
3247
|
-
* missing English translation is a compile error, not a runtime miss.
|
|
3248
|
-
*/
|
|
3249
|
-
const zh = {
|
|
3250
|
-
/** aria-label on each dot: `跳转到第 ${i + 1} 条消息`. */
|
|
3251
|
-
"jump.to": "跳转到第 {n} 条消息",
|
|
3252
|
-
/** Load-older coverage hint: `已显示 {marks.length} 条 · 还有更早`. */
|
|
3253
|
-
"window.hint": "已显示 {n} 条 · 还有更早",
|
|
3254
|
-
/** Hover turn badge: `第 ${mark.turn} 轮`. */
|
|
3255
|
-
"turn.label": "第 {n} 轮",
|
|
3256
|
-
/** Hover position: `第 {hover.index + 1} / {hover.total} 条`. */
|
|
3257
|
-
"pos.of": "第 {n} / {m} 条",
|
|
3258
|
-
/** Search input placeholder. */
|
|
3259
|
-
"search.placeholder": "搜索消息内容",
|
|
3260
|
-
/** aria-label on the search toggle button and the search input. */
|
|
3261
|
-
"search.label": "搜索消息",
|
|
3262
|
-
/** aria-label on the bookmarks-only filter toggle. */
|
|
3263
|
-
"bookmark.filter": "只看收藏",
|
|
3264
|
-
/** aria-label + title on the focus-mode toggle when focus is OFF (arm it). */
|
|
3265
|
-
"focus.on": "聚焦模式",
|
|
3266
|
-
/** aria-label + title on the focus-mode toggle when focus is ON (disarm it). */
|
|
3267
|
-
"focus.off": "退出聚焦",
|
|
3268
|
-
/** aria-label on the hover tooltip star toggle. */
|
|
3269
|
-
"bookmark.star": "收藏此消息",
|
|
3270
|
-
/** aria-label on the search clear button. */
|
|
3271
|
-
"search.clear": "清空搜索",
|
|
3272
|
-
/** title + aria-label on the load-older `···` button. */
|
|
3273
|
-
"load.older": "加载更早消息",
|
|
3274
|
-
/** aria-label on the rail root. */
|
|
3275
|
-
"rail.label": "会话里程碑",
|
|
3276
|
-
/** aria-label on the dot list. */
|
|
3277
|
-
"rail.list": "会话里程碑列表",
|
|
3278
|
-
/** Hover preview fallback for empty message text. */
|
|
3279
|
-
"no.text": "(无文本)",
|
|
3280
|
-
/** Relative time: `< 60s`. */
|
|
3281
|
-
"time.justNow": "刚刚",
|
|
3282
|
-
/** Relative time: `< 1h`. */
|
|
3283
|
-
"time.minutes": "{n} 分钟前",
|
|
3284
|
-
/** Relative time: `< 1d`. */
|
|
3285
|
-
"time.hours": "{n} 小时前",
|
|
3286
|
-
/** Relative time: `>= 1d`. */
|
|
3287
|
-
"time.days": "{n} 天前",
|
|
3288
|
-
/** Hover duration: `用时 {durationLabel}`. */
|
|
3289
|
-
"duration.label": "用时 {name}",
|
|
3290
|
-
/** Hover TTFT: `首字 {ttftLabel}`. */
|
|
3291
|
-
"ttft.label": "首字 {name}",
|
|
3292
|
-
/** TurnEndReason `completed`. */
|
|
3293
|
-
"reason.completed": "已完成",
|
|
3294
|
-
/** TurnEndReason `aborted`. */
|
|
3295
|
-
"reason.aborted": "已中止",
|
|
3296
|
-
/** TurnEndReason `error`. */
|
|
3297
|
-
"reason.error": "出错",
|
|
3298
|
-
/** TurnEndReason `max-tokens`. */
|
|
3299
|
-
"reason.maxTokens": "达到上限",
|
|
3300
|
-
/** TurnEndReason `interrupted`. */
|
|
3301
|
-
"reason.interrupted": "已中断",
|
|
3302
|
-
/** TurnEndReason `blocked`. */
|
|
3303
|
-
"reason.blocked": "已阻塞",
|
|
3304
|
-
/** Copy-message tooltip action. */
|
|
3305
|
-
"copy.message": "复制消息",
|
|
3306
|
-
/** Fork-from-here tooltip action. */
|
|
3307
|
-
"fork.here": "从此处 fork",
|
|
3308
|
-
/** Collapse-turn tooltip action. */
|
|
3309
|
-
"collapse.turn": "折叠此轮",
|
|
3310
|
-
/** Expand-turn tooltip action. */
|
|
3311
|
-
"expand.turn": "展开此轮",
|
|
3312
|
-
/** aria-label + title on the milestone-list toggle when the panel is CLOSED. */
|
|
3313
|
-
"list.open": "打开列表",
|
|
3314
|
-
/** aria-label + title on the milestone-list toggle when the panel is OPEN. */
|
|
3315
|
-
"list.close": "收起列表",
|
|
3316
|
-
/** Header title of the all-prompts list panel. */
|
|
3317
|
-
"list.label": "全部提问",
|
|
3318
|
-
/** Header title + input placeholder of the cross-session search panel. */
|
|
3319
|
-
"search.cross": "跨会话搜索",
|
|
3320
|
-
/** aria-label + title on the cross-session search toggle when the panel is CLOSED. */
|
|
3321
|
-
"search.cross.open": "打开跨会话搜索",
|
|
3322
|
-
/** aria-label + title on the cross-session search toggle when the panel is OPEN. */
|
|
3323
|
-
"search.cross.close": "收起跨会话搜索",
|
|
3324
|
-
/** Cross-session result row title fallback for sessions with no display title. */
|
|
3325
|
-
"search.untitled": "(无标题)",
|
|
3326
|
-
/** Cross-session search failure notice. */
|
|
3327
|
-
"search.error": "搜索失败,请重试",
|
|
3328
|
-
/** Cross-session search footer hint when the harness capped the result list. */
|
|
3329
|
-
"search.more": "结果已截断,请细化关键词",
|
|
3330
|
-
/** aria-label on the toolbar expand arrow while the toolbar is COLLAPSED (expand it). */
|
|
3331
|
-
"toolbar.expand": "展开工具栏",
|
|
3332
|
-
/** aria-label on the toolbar expand arrow while the toolbar is EXPANDED (collapse it). */
|
|
3333
|
-
"toolbar.collapse": "收起工具栏",
|
|
3334
|
-
/** aria-label on the toolbar settings gear while the settings menu is CLOSED. */
|
|
3335
|
-
"toolbar.settings.open": "打开设置",
|
|
3336
|
-
/** aria-label on the toolbar settings gear while the settings menu is OPEN. */
|
|
3337
|
-
"toolbar.settings.close": "关闭设置",
|
|
3338
|
-
/** Header title of the toolbar settings menu. */
|
|
3339
|
-
"settings.title": "设置",
|
|
3340
|
-
/** Per-feature toggle label inside the settings menu: keep visible while collapsed. */
|
|
3341
|
-
"settings.pin": "在折叠外显示",
|
|
3342
|
-
/** Settings action that clears every pinned feature. */
|
|
3343
|
-
"settings.reset": "恢复默认",
|
|
3344
|
-
/** Settings footer heading above the project links. */
|
|
3345
|
-
"settings.support": "支持我们",
|
|
3346
|
-
/** Settings footer link: the GitHub repository. */
|
|
3347
|
-
"settings.repo": "GitHub 仓库",
|
|
3348
|
-
/** Settings footer link: star the repository. */
|
|
3349
|
-
"settings.star": "欢迎 Star ★",
|
|
3350
|
-
/** Settings footer link: file an issue. */
|
|
3351
|
-
"settings.issues": "提交 Issue",
|
|
3352
|
-
/** Settings footer link: the npm install channel. */
|
|
3353
|
-
"settings.npm": "npm 安装渠道",
|
|
3354
|
-
/** B4 update-check: toolbar button label + title/aria-label. */
|
|
3355
|
-
"update.check": "检查更新",
|
|
3356
|
-
/** B4 update-check: popover title. */
|
|
3357
|
-
"update.title": "更新检测",
|
|
3358
|
-
/** B4 update-check: installed-version row label. */
|
|
3359
|
-
"update.current": "当前版本",
|
|
3360
|
-
/** B4 update-check: newest-published-version row label. */
|
|
3361
|
-
"update.latest": "最新版本",
|
|
3362
|
-
/** B4 update-check: conclusion when the installed version is current. */
|
|
3363
|
-
"update.upToDate": "已是最新版本",
|
|
3364
|
-
/** B4 update-check: conclusion when a newer version exists. */
|
|
3365
|
-
"update.available": "发现新版本",
|
|
3366
|
-
/** B4 update-check: link text for the npm upgrade channel. */
|
|
3367
|
-
"update.goNpm": "去 npm 升级",
|
|
3368
|
-
/** B4 update-check: supported-host-lines metadata row label. */
|
|
3369
|
-
"update.hostLines": "已适配官方版本线",
|
|
3370
|
-
/** B4 update-check: in-flight state of the manual check button. */
|
|
3371
|
-
"update.checking": "检查中…",
|
|
3372
|
-
/** B4 update-check: failed state heading. */
|
|
3373
|
-
"update.failed": "检查失败",
|
|
3374
|
-
/** B4 update-check: retry action inside the failed state. */
|
|
3375
|
-
"update.retry": "重试"
|
|
3376
|
-
};
|
|
3377
|
-
const en = {
|
|
3378
|
-
"jump.to": "Jump to message {n}",
|
|
3379
|
-
"window.hint": "Showing {n} messages · more below",
|
|
3380
|
-
"turn.label": "Turn {n}",
|
|
3381
|
-
"pos.of": "Message {n} of {m}",
|
|
3382
|
-
"search.placeholder": "Search message content",
|
|
3383
|
-
"search.label": "Search messages",
|
|
3384
|
-
"bookmark.filter": "Bookmarks only",
|
|
3385
|
-
"focus.on": "Focus mode",
|
|
3386
|
-
"focus.off": "Exit focus",
|
|
3387
|
-
"bookmark.star": "Bookmark this message",
|
|
3388
|
-
"search.clear": "Clear search",
|
|
3389
|
-
"load.older": "Load older messages",
|
|
3390
|
-
"rail.label": "Session milestones",
|
|
3391
|
-
"rail.list": "Session milestone list",
|
|
3392
|
-
"no.text": "(no text)",
|
|
3393
|
-
"time.justNow": "Just now",
|
|
3394
|
-
"time.minutes": "{n} minutes ago",
|
|
3395
|
-
"time.hours": "{n} hours ago",
|
|
3396
|
-
"time.days": "{n} days ago",
|
|
3397
|
-
"duration.label": "Duration {name}",
|
|
3398
|
-
"ttft.label": "First token {name}",
|
|
3399
|
-
"reason.completed": "Completed",
|
|
3400
|
-
"reason.aborted": "Aborted",
|
|
3401
|
-
"reason.error": "Error",
|
|
3402
|
-
"reason.maxTokens": "Max tokens reached",
|
|
3403
|
-
"reason.interrupted": "Interrupted",
|
|
3404
|
-
"reason.blocked": "Blocked",
|
|
3405
|
-
"copy.message": "Copy message",
|
|
3406
|
-
"fork.here": "Fork from here",
|
|
3407
|
-
"collapse.turn": "Collapse turn",
|
|
3408
|
-
"expand.turn": "Expand turn",
|
|
3409
|
-
"list.open": "Open list",
|
|
3410
|
-
"list.close": "Close list",
|
|
3411
|
-
"list.label": "All prompts",
|
|
3412
|
-
"search.cross": "Cross-session search",
|
|
3413
|
-
"search.cross.open": "Open cross-session search",
|
|
3414
|
-
"search.cross.close": "Close cross-session search",
|
|
3415
|
-
"search.untitled": "(untitled)",
|
|
3416
|
-
"search.error": "Search failed, retry",
|
|
3417
|
-
"search.more": "Results truncated — refine your query",
|
|
3418
|
-
"toolbar.expand": "Expand toolbar",
|
|
3419
|
-
"toolbar.collapse": "Collapse toolbar",
|
|
3420
|
-
"toolbar.settings.open": "Open settings",
|
|
3421
|
-
"toolbar.settings.close": "Close settings",
|
|
3422
|
-
"settings.title": "Settings",
|
|
3423
|
-
"settings.pin": "Show outside collapse",
|
|
3424
|
-
"settings.reset": "Restore defaults",
|
|
3425
|
-
"settings.support": "Support us",
|
|
3426
|
-
"settings.repo": "GitHub repo",
|
|
3427
|
-
"settings.star": "Give us a Star ★",
|
|
3428
|
-
"settings.issues": "Report an Issue",
|
|
3429
|
-
"settings.npm": "Install via npm",
|
|
3430
|
-
"update.check": "Check updates",
|
|
3431
|
-
"update.title": "Update check",
|
|
3432
|
-
"update.current": "Current version",
|
|
3433
|
-
"update.latest": "Latest version",
|
|
3434
|
-
"update.upToDate": "You are up to date",
|
|
3435
|
-
"update.available": "Update available",
|
|
3436
|
-
"update.goNpm": "Upgrade on npm",
|
|
3437
|
-
"update.hostLines": "Supported official version lines",
|
|
3438
|
-
"update.checking": "Checking…",
|
|
3439
|
-
"update.failed": "Check failed",
|
|
3440
|
-
"update.retry": "Retry"
|
|
3441
|
-
};
|
|
3442
|
-
//#endregion
|
|
3443
4699
|
//#region src/client/index.ts
|
|
3444
4700
|
/** Required services (cordis fiber inject). */
|
|
3445
4701
|
const inject = [
|