dsh-milestone 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -134
- package/assets/demo.svg +39 -0
- package/assets/logo.svg +22 -0
- package/lib/client.js +1411 -664
- 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,312 @@ 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 action that clears every pinned feature. */
|
|
686
|
+
"settings.reset": "恢复默认",
|
|
687
|
+
/** Settings footer heading above the project links. */
|
|
688
|
+
"settings.support": "支持我们",
|
|
689
|
+
/** Settings footer link: the GitHub repository. */
|
|
690
|
+
"settings.repo": "GitHub 仓库",
|
|
691
|
+
/** Settings footer link: star the repository. */
|
|
692
|
+
"settings.star": "欢迎 Star ★",
|
|
693
|
+
/** Settings footer link: file an issue. */
|
|
694
|
+
"settings.issues": "提交 Issue",
|
|
695
|
+
/** Settings footer link: the npm install channel. */
|
|
696
|
+
"settings.npm": "npm 安装渠道",
|
|
697
|
+
/** Settings: feature-row name of the settings key itself (registry row). */
|
|
698
|
+
"settings.label": "设置",
|
|
699
|
+
/** aria-label on the settings modal close button. */
|
|
700
|
+
"settings.close": "关闭",
|
|
701
|
+
/** Settings modal: section heading for the feature pin rows. */
|
|
702
|
+
"settings.section.features": "功能与快捷区",
|
|
703
|
+
/** Settings modal: section heading for the personalization controls. */
|
|
704
|
+
"settings.section.personal": "个性化",
|
|
705
|
+
/** Settings: hover description — in-rail search. */
|
|
706
|
+
"settings.desc.search": "按完整消息内容过滤并跳转到对应消息",
|
|
707
|
+
/** Settings: hover description — all-prompts list. */
|
|
708
|
+
"settings.desc.list": "本会话全部提问一览",
|
|
709
|
+
/** Settings: hover description — cross-session search. */
|
|
710
|
+
"settings.desc.sessionSearch": "跨会话搜索所有会话",
|
|
711
|
+
/** Settings: hover description — bookmarks filter. */
|
|
712
|
+
"settings.desc.bookmarks": "只显示已收藏的消息",
|
|
713
|
+
/** Settings: hover description — focus mode. */
|
|
714
|
+
"settings.desc.focus": "淡化 AI 思考块,阅读更清爽",
|
|
715
|
+
/** Settings: hover description — update check. */
|
|
716
|
+
"settings.desc.updateCheck": "检查 npm 是否有新版本",
|
|
717
|
+
/** Settings: hover description — the settings key itself. */
|
|
718
|
+
"settings.desc.settings": "自定义工具栏与外观",
|
|
719
|
+
/** Settings personalization: accent color row label. */
|
|
720
|
+
"settings.accent": "强调色",
|
|
721
|
+
/** Settings personalization: custom color swatch label. */
|
|
722
|
+
"settings.custom": "自定义",
|
|
723
|
+
/** Settings personalization: icon/dot size slider label. */
|
|
724
|
+
"settings.iconSize": "图标 / 圆点大小",
|
|
725
|
+
/** Settings personalization: edge-distance slider label. */
|
|
726
|
+
"settings.inset": "距侧边距离",
|
|
727
|
+
/** Settings personalization: rail side row label. */
|
|
728
|
+
"settings.side": "位置",
|
|
729
|
+
/** Settings personalization: side radio — hug the left edge. */
|
|
730
|
+
"settings.side.left": "左侧",
|
|
731
|
+
/** Settings personalization: side radio — hug the right edge. */
|
|
732
|
+
"settings.side.right": "右侧",
|
|
733
|
+
/** B4 update-check: toolbar button label + title/aria-label. */
|
|
734
|
+
"update.check": "检查更新",
|
|
735
|
+
/** B4 update-check: popover title. */
|
|
736
|
+
"update.title": "更新检测",
|
|
737
|
+
/** B4 update-check: installed-version row label. */
|
|
738
|
+
"update.current": "当前版本",
|
|
739
|
+
/** B4 update-check: newest-published-version row label. */
|
|
740
|
+
"update.latest": "最新版本",
|
|
741
|
+
/** B4 update-check: conclusion when the installed version is current. */
|
|
742
|
+
"update.upToDate": "已是最新版本",
|
|
743
|
+
/** B4 update-check: conclusion when a newer version exists. */
|
|
744
|
+
"update.available": "发现新版本",
|
|
745
|
+
/** B4 update-check: link text for the npm upgrade channel. */
|
|
746
|
+
"update.goNpm": "去 npm 升级",
|
|
747
|
+
/** B4 update-check: supported-host-lines metadata row label. */
|
|
748
|
+
"update.hostLines": "已适配官方版本线",
|
|
749
|
+
/** B4 update-check: in-flight state of the manual check button. */
|
|
750
|
+
"update.checking": "检查中…",
|
|
751
|
+
/** B4 update-check: failed state heading. */
|
|
752
|
+
"update.failed": "检查失败",
|
|
753
|
+
/** B4 update-check: retry action inside the failed state. */
|
|
754
|
+
"update.retry": "重试",
|
|
755
|
+
/** Settings modal section title: language. */
|
|
756
|
+
"settings.language": "语言",
|
|
757
|
+
/** Language option: follow the harness UI language. */
|
|
758
|
+
"settings.lang.system": "跟随系统",
|
|
759
|
+
/** Language option: force Chinese copy. */
|
|
760
|
+
"settings.lang.zh": "中文",
|
|
761
|
+
/** Language option: force English copy. */
|
|
762
|
+
"settings.lang.en": "English"
|
|
763
|
+
};
|
|
764
|
+
const en = {
|
|
765
|
+
"jump.to": "Jump to message {n}",
|
|
766
|
+
"window.hint": "Showing {n} messages · more below",
|
|
767
|
+
"turn.label": "Turn {n}",
|
|
768
|
+
"pos.of": "Message {n} of {m}",
|
|
769
|
+
"search.placeholder": "Search message content",
|
|
770
|
+
"search.label": "Search messages",
|
|
771
|
+
"bookmark.filter": "Bookmarks only",
|
|
772
|
+
"focus.on": "Focus mode",
|
|
773
|
+
"focus.off": "Exit focus",
|
|
774
|
+
"bookmark.star": "Bookmark this message",
|
|
775
|
+
"search.clear": "Clear search",
|
|
776
|
+
"load.older": "Load older messages",
|
|
777
|
+
"rail.label": "Session milestones",
|
|
778
|
+
"rail.list": "Session milestone list",
|
|
779
|
+
"no.text": "(no text)",
|
|
780
|
+
"time.justNow": "Just now",
|
|
781
|
+
"time.minutes": "{n} minutes ago",
|
|
782
|
+
"time.hours": "{n} hours ago",
|
|
783
|
+
"time.days": "{n} days ago",
|
|
784
|
+
"duration.label": "Duration {name}",
|
|
785
|
+
"ttft.label": "First token {name}",
|
|
786
|
+
"reason.completed": "Completed",
|
|
787
|
+
"reason.aborted": "Aborted",
|
|
788
|
+
"reason.error": "Error",
|
|
789
|
+
"reason.maxTokens": "Max tokens reached",
|
|
790
|
+
"reason.interrupted": "Interrupted",
|
|
791
|
+
"reason.blocked": "Blocked",
|
|
792
|
+
"copy.message": "Copy message",
|
|
793
|
+
"fork.here": "Fork from here",
|
|
794
|
+
"collapse.turn": "Collapse turn",
|
|
795
|
+
"expand.turn": "Expand turn",
|
|
796
|
+
"list.open": "Open list",
|
|
797
|
+
"list.close": "Close list",
|
|
798
|
+
"list.label": "All prompts",
|
|
799
|
+
"search.cross": "Cross-session search",
|
|
800
|
+
"search.cross.open": "Open cross-session search",
|
|
801
|
+
"search.cross.close": "Close cross-session search",
|
|
802
|
+
"search.untitled": "(untitled)",
|
|
803
|
+
"search.error": "Search failed, retry",
|
|
804
|
+
"search.more": "Results truncated — refine your query",
|
|
805
|
+
"toolbar.expand": "Expand toolbar",
|
|
806
|
+
"toolbar.collapse": "Collapse toolbar",
|
|
807
|
+
"toolbar.settings.open": "Open settings",
|
|
808
|
+
"toolbar.settings.close": "Close settings",
|
|
809
|
+
"settings.title": "Settings",
|
|
810
|
+
"settings.pin": "Show outside collapse",
|
|
811
|
+
"settings.reset": "Restore defaults",
|
|
812
|
+
"settings.support": "Support us",
|
|
813
|
+
"settings.repo": "GitHub repo",
|
|
814
|
+
"settings.star": "Give us a Star ★",
|
|
815
|
+
"settings.issues": "Report an Issue",
|
|
816
|
+
"settings.npm": "Install via npm",
|
|
817
|
+
"settings.label": "Settings",
|
|
818
|
+
"settings.close": "Close",
|
|
819
|
+
"settings.section.features": "Features & Shortcuts",
|
|
820
|
+
"settings.section.personal": "Personalization",
|
|
821
|
+
"settings.desc.search": "Filter by full message text and jump to the match",
|
|
822
|
+
"settings.desc.list": "Overview of every prompt in this session",
|
|
823
|
+
"settings.desc.sessionSearch": "Search across all sessions",
|
|
824
|
+
"settings.desc.bookmarks": "Show bookmarked messages only",
|
|
825
|
+
"settings.desc.focus": "Dim AI thinking blocks for a cleaner read",
|
|
826
|
+
"settings.desc.updateCheck": "Check npm for a newer release",
|
|
827
|
+
"settings.desc.settings": "Customize the toolbar and appearance",
|
|
828
|
+
"settings.accent": "Accent color",
|
|
829
|
+
"settings.custom": "Custom",
|
|
830
|
+
"settings.iconSize": "Icon / dot size",
|
|
831
|
+
"settings.inset": "Distance from the edge",
|
|
832
|
+
"settings.side": "Position",
|
|
833
|
+
"settings.side.left": "Left",
|
|
834
|
+
"settings.side.right": "Right",
|
|
835
|
+
"update.check": "Check updates",
|
|
836
|
+
"update.title": "Update check",
|
|
837
|
+
"update.current": "Current version",
|
|
838
|
+
"update.latest": "Latest version",
|
|
839
|
+
"update.upToDate": "You are up to date",
|
|
840
|
+
"update.available": "Update available",
|
|
841
|
+
"update.goNpm": "Upgrade on npm",
|
|
842
|
+
"update.hostLines": "Supported official version lines",
|
|
843
|
+
"update.checking": "Checking…",
|
|
844
|
+
"update.failed": "Check failed",
|
|
845
|
+
"update.retry": "Retry",
|
|
846
|
+
"settings.language": "Language",
|
|
847
|
+
"settings.lang.system": "Follow system",
|
|
848
|
+
"settings.lang.zh": "Chinese",
|
|
849
|
+
"settings.lang.en": "English"
|
|
850
|
+
};
|
|
851
|
+
/**
|
|
852
|
+
* Interpolate `{name}` placeholders with params, matching the harness t seat's
|
|
853
|
+
* substitution shape; an unknown parameter leaves the placeholder verbatim.
|
|
854
|
+
*/
|
|
855
|
+
function interpolate(template, params) {
|
|
856
|
+
if (params === void 0) return template;
|
|
857
|
+
return template.replace(/\{([A-Za-z][A-Za-z0-9]*)\}/g, (slot, name) => name in params ? String(params[name]) : slot);
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Dictionary-backed translate for the forced-language override (locale prefs
|
|
861
|
+
* 'zh' / 'en'): resolves a key against the plugin's own dictionaries with
|
|
862
|
+
* placeholder interpolation; unknown keys pass through unchanged (same
|
|
863
|
+
* degradation as the harness seat).
|
|
435
864
|
*/
|
|
436
|
-
function
|
|
437
|
-
|
|
865
|
+
function translateDict(dict, key, params) {
|
|
866
|
+
const template = dict[key];
|
|
867
|
+
return template === void 0 ? key : interpolate(template, params);
|
|
438
868
|
}
|
|
439
869
|
//#endregion
|
|
440
870
|
//#region src/client/turn-group-logic.ts
|
|
@@ -1300,20 +1730,29 @@ window.__ModuleLoader__.load({
|
|
|
1300
1730
|
//#endregion
|
|
1301
1731
|
//#region src/client/toolbar-prefs.ts
|
|
1302
1732
|
/**
|
|
1303
|
-
* toolbar-prefs: the persistence layer for the milestone rail's
|
|
1304
|
-
*
|
|
1733
|
+
* toolbar-prefs: the persistence layer for the milestone rail's toolbar
|
|
1734
|
+
* personalization — WHICH function keys stay visible outside the collapse
|
|
1735
|
+
* (pinned) plus the settings-module appearance prefs (accent color, icon/dot
|
|
1736
|
+
* size, distance from the rail's screen edge, and rail side).
|
|
1737
|
+
*
|
|
1738
|
+
* Storage contract: one localStorage key (`dsh-milestone.toolbar`) holding a
|
|
1739
|
+
* JSON object:
|
|
1305
1740
|
*
|
|
1306
|
-
*
|
|
1307
|
-
*
|
|
1308
|
-
* wants to keep visible while the toolbar is COLLAPSED. An absent or corrupt
|
|
1309
|
-
* value degrades to `[]` (everything folded away).
|
|
1741
|
+
* { "pinned": string[], "accent": "#rrggbb", "iconSize": number,
|
|
1742
|
+
* "inset": number, "side": "left" | "right", "locale": "system"|"zh"|"en" }
|
|
1310
1743
|
*
|
|
1311
|
-
*
|
|
1312
|
-
* (
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*
|
|
1744
|
+
* Backward compatibility: the pre-personalization blob `{ "pinned": string[] }`
|
|
1745
|
+
* (and an entirely absent value) parses to the DEFAULT prefs with the new
|
|
1746
|
+
* fields at their defaults — old users keep their pins untouched.
|
|
1747
|
+
*
|
|
1748
|
+
* All reads are sanitized per field:
|
|
1749
|
+
* - `pinned`: whitelisted ids only (`TOOLBAR_PIN_IDS`), duplicates dropped,
|
|
1750
|
+
* first-seen (pin) order preserved;
|
|
1751
|
+
* - `accent`: a canonical `#rrggbb` hex, lowercased; anything else falls
|
|
1752
|
+
* back to the default blue;
|
|
1753
|
+
* - `iconSize` / `inset`: finite numbers snapped to the slider step
|
|
1754
|
+
* (even values) and clamped to the slider range;
|
|
1755
|
+
* - `side`: exactly `'left'` or `'right'`.
|
|
1317
1756
|
*
|
|
1318
1757
|
* The whitelist lives HERE (not in MilestoneRail) so the pure functions stay
|
|
1319
1758
|
* dependency-free and unit-testable; MilestoneRail's feature registry keys
|
|
@@ -1323,8 +1762,10 @@ window.__ModuleLoader__.load({
|
|
|
1323
1762
|
const TOOLBAR_PREFS_KEY = "dsh-milestone.toolbar";
|
|
1324
1763
|
/**
|
|
1325
1764
|
* Canonical function-key ids that may be pinned outside the collapse, in
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1765
|
+
* render order. `settings` is a REGULAR feature since the B-design move: the
|
|
1766
|
+
* gear left the always-visible chrome and now sits at the end of the expanded
|
|
1767
|
+
* feature queue (default unpinned). Adding a feature here (plus its registry
|
|
1768
|
+
* entry in MilestoneRail) is the whole "pin it" extension point.
|
|
1328
1769
|
*/
|
|
1329
1770
|
const TOOLBAR_PIN_IDS = [
|
|
1330
1771
|
"search",
|
|
@@ -1332,32 +1773,28 @@ window.__ModuleLoader__.load({
|
|
|
1332
1773
|
"sessionSearch",
|
|
1333
1774
|
"bookmarks",
|
|
1334
1775
|
"focus",
|
|
1335
|
-
"updateCheck"
|
|
1776
|
+
"updateCheck",
|
|
1777
|
+
"settings"
|
|
1336
1778
|
];
|
|
1779
|
+
/** The canonical default prefs ("恢复默认" target; also the read fallback). */
|
|
1780
|
+
const DEFAULT_PREFS = {
|
|
1781
|
+
pinned: [],
|
|
1782
|
+
accent: "#4d7cfd",
|
|
1783
|
+
iconSize: 28,
|
|
1784
|
+
inset: 14,
|
|
1785
|
+
side: "right",
|
|
1786
|
+
locale: "system"
|
|
1787
|
+
};
|
|
1337
1788
|
/** Type guard for registry ids — unknown strings never survive a parse. */
|
|
1338
1789
|
function isToolbarPinId(id) {
|
|
1339
1790
|
return TOOLBAR_PIN_IDS.includes(id);
|
|
1340
1791
|
}
|
|
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 [];
|
|
1792
|
+
/** Whitelist + dedupe + first-seen-order sanitizer for the pinned list. */
|
|
1793
|
+
function sanitizePinned(raw) {
|
|
1794
|
+
if (!Array.isArray(raw)) return [];
|
|
1358
1795
|
const seen = /* @__PURE__ */ new Set();
|
|
1359
1796
|
const result = [];
|
|
1360
|
-
for (const id of
|
|
1797
|
+
for (const id of raw) {
|
|
1361
1798
|
if (typeof id !== "string" || !isToolbarPinId(id) || seen.has(id)) continue;
|
|
1362
1799
|
seen.add(id);
|
|
1363
1800
|
result.push(id);
|
|
@@ -1365,42 +1802,78 @@ window.__ModuleLoader__.load({
|
|
|
1365
1802
|
return result;
|
|
1366
1803
|
}
|
|
1367
1804
|
/**
|
|
1805
|
+
* Snap a finite number to the nearest `step` inside [min, max]; any non-finite
|
|
1806
|
+
* or non-number input falls back to `fallback`. Used for both sliders so a
|
|
1807
|
+
* hand-edited blob (e.g. `iconSize: 21`) converges on a legal slider value.
|
|
1808
|
+
*/
|
|
1809
|
+
function clampStep(value, min, max, step, fallback) {
|
|
1810
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
1811
|
+
const snapped = Math.round(Math.min(max, Math.max(min, value)) / step) * step;
|
|
1812
|
+
return Math.min(max, Math.max(min, snapped));
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Parse + sanitize the raw persisted blob: `null` (nothing stored), invalid
|
|
1816
|
+
* JSON, or a non-object shape all degrade to the DEFAULT prefs. Each field is
|
|
1817
|
+
* sanitized independently, so a half-corrupt blob keeps its valid parts
|
|
1818
|
+
* (e.g. an old `{pinned}`-only blob gains the default accent/size/inset/side).
|
|
1819
|
+
*/
|
|
1820
|
+
function parsePrefs(raw) {
|
|
1821
|
+
if (raw === null) return { ...DEFAULT_PREFS };
|
|
1822
|
+
let parsed;
|
|
1823
|
+
try {
|
|
1824
|
+
parsed = JSON.parse(raw);
|
|
1825
|
+
} catch {
|
|
1826
|
+
return { ...DEFAULT_PREFS };
|
|
1827
|
+
}
|
|
1828
|
+
if (typeof parsed !== "object" || parsed === null) return { ...DEFAULT_PREFS };
|
|
1829
|
+
const { pinned, accent, iconSize, inset, side, locale } = parsed;
|
|
1830
|
+
return {
|
|
1831
|
+
pinned: sanitizePinned(pinned),
|
|
1832
|
+
accent: typeof accent === "string" && isHexColor(accent) ? accent.toLowerCase() : DEFAULT_PREFS.accent,
|
|
1833
|
+
iconSize: clampStep(iconSize, 20, 36, 2, DEFAULT_PREFS.iconSize),
|
|
1834
|
+
inset: clampStep(inset, 0, 40, 2, DEFAULT_PREFS.inset),
|
|
1835
|
+
side: side === "left" || side === "right" ? side : DEFAULT_PREFS.side,
|
|
1836
|
+
locale: locale === "zh" || locale === "en" || locale === "system" ? locale : DEFAULT_PREFS.locale
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1368
1840
|
* Read + sanitize the persisted toolbar prefs from localStorage. Degrades to
|
|
1369
|
-
*
|
|
1370
|
-
* best-effort enhancement, never a render blocker.
|
|
1841
|
+
* the DEFAULT prefs when storage is unavailable (SSR, sandboxed iframe) —
|
|
1842
|
+
* personalization is a best-effort enhancement, never a render blocker.
|
|
1371
1843
|
*/
|
|
1372
1844
|
function loadPrefs() {
|
|
1373
1845
|
try {
|
|
1374
1846
|
return parsePrefs(localStorage.getItem(TOOLBAR_PREFS_KEY));
|
|
1375
1847
|
} catch {
|
|
1376
|
-
return
|
|
1848
|
+
return { ...DEFAULT_PREFS };
|
|
1377
1849
|
}
|
|
1378
1850
|
}
|
|
1379
1851
|
/**
|
|
1380
|
-
* Persist the
|
|
1381
|
-
* value is never written).
|
|
1382
|
-
*
|
|
1383
|
-
* rejected. Swallows storage failures for the same best-effort reason as
|
|
1384
|
-
* {@link loadPrefs}.
|
|
1852
|
+
* Persist the full prefs (sanitized on the way out so a corrupt in-memory
|
|
1853
|
+
* value is never written). Swallows storage failures for the same best-effort
|
|
1854
|
+
* reason as {@link loadPrefs}.
|
|
1385
1855
|
*/
|
|
1386
|
-
function savePrefs(
|
|
1387
|
-
const cleaned = parsePrefs(JSON.stringify(
|
|
1856
|
+
function savePrefs(prefs) {
|
|
1857
|
+
const cleaned = parsePrefs(JSON.stringify(prefs));
|
|
1388
1858
|
try {
|
|
1389
|
-
localStorage.setItem(TOOLBAR_PREFS_KEY, JSON.stringify(
|
|
1859
|
+
localStorage.setItem(TOOLBAR_PREFS_KEY, JSON.stringify(cleaned));
|
|
1390
1860
|
} catch {}
|
|
1391
1861
|
}
|
|
1392
1862
|
/**
|
|
1393
1863
|
* Pure toggle: adds `id` to the pinned set when absent, removes it when
|
|
1394
|
-
* present. Unknown ids are ignored (
|
|
1395
|
-
*
|
|
1864
|
+
* present. Unknown ids are ignored (prefs returned unchanged) and the pinned
|
|
1865
|
+
* set is always deduped via the sanitizer, so callers can feed the result
|
|
1396
1866
|
* straight back into {@link savePrefs}.
|
|
1397
1867
|
*/
|
|
1398
|
-
function togglePin(
|
|
1399
|
-
if (!isToolbarPinId(id)) return
|
|
1400
|
-
const next = new Set(pinned);
|
|
1868
|
+
function togglePin(prefs, id) {
|
|
1869
|
+
if (!isToolbarPinId(id)) return { ...prefs };
|
|
1870
|
+
const next = new Set(prefs.pinned);
|
|
1401
1871
|
if (next.has(id)) next.delete(id);
|
|
1402
1872
|
else next.add(id);
|
|
1403
|
-
return
|
|
1873
|
+
return {
|
|
1874
|
+
...prefs,
|
|
1875
|
+
pinned: sanitizePinned([...next])
|
|
1876
|
+
};
|
|
1404
1877
|
}
|
|
1405
1878
|
//#endregion
|
|
1406
1879
|
//#region src/client/version-logic.ts
|
|
@@ -1706,12 +2179,12 @@ window.__ModuleLoader__.load({
|
|
|
1706
2179
|
* Installed plugin version. Injected at build time as
|
|
1707
2180
|
* `__DSH_MILESTONE_VERSION__`; falls back to `0.0.0-dev` when unbuilt.
|
|
1708
2181
|
*/
|
|
1709
|
-
const PLUGIN_VERSION = "0.6.
|
|
2182
|
+
const PLUGIN_VERSION = "0.6.2";
|
|
1710
2183
|
//#endregion
|
|
1711
2184
|
//#region src/client/MilestoneRail.tsx
|
|
1712
2185
|
/**
|
|
1713
2186
|
* MilestoneRail: the milestone.rail entry (session scope). Renders a fixed
|
|
1714
|
-
*
|
|
2187
|
+
* side vertical scrubber as a **fixed-pitch dot list** (like a git commit
|
|
1715
2188
|
* graph), NOT a minimap: one dot per user message, equal spacing regardless of
|
|
1716
2189
|
* conversation length. The list itself scrolls with the wheel when it outgrows
|
|
1717
2190
|
* the viewport; hovering a dot shows rich metadata (time, turn, duration, end
|
|
@@ -1723,14 +2196,16 @@ window.__ModuleLoader__.load({
|
|
|
1723
2196
|
* and the ui-conversation 'turn-tail'
|
|
1724
2197
|
* location data (ttftMs/tokensPerSecond)
|
|
1725
2198
|
*
|
|
1726
|
-
* Positioning: the rail hugs the conversation scrollport's
|
|
1727
|
-
*
|
|
2199
|
+
* Positioning: the rail hugs the conversation scrollport's chosen screen edge
|
|
2200
|
+
* (settings 位置: left or right), offset a little inward so it clears the
|
|
2201
|
+
* native scrollbar and sits near the prose.
|
|
1728
2202
|
*
|
|
1729
2203
|
* 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
|
|
2204
|
+
* panel on the rail's free side with a message-text search input; matches
|
|
2205
|
+
* light up the dots (non-matches dim), Enter cycles the active match
|
|
2206
|
+
* (wrapping) and jumps to it, Escape clears and closes. Matching runs over the
|
|
2207
|
+
* FULL message text (`text` from rail-logic.extractText), not the truncated
|
|
2208
|
+
* hover preview.
|
|
1734
2209
|
*
|
|
1735
2210
|
* Current-position highlight (F2): the dot for the user message at/just above
|
|
1736
2211
|
* the conversation viewport top carries a white ring (`useCurrentAnchor`
|
|
@@ -1739,8 +2214,15 @@ window.__ModuleLoader__.load({
|
|
|
1739
2214
|
* Load-older + window coverage (F3): when the session still has earlier pages
|
|
1740
2215
|
* (`hasMore`) a slim `···` button sits at the rail top and triggers the
|
|
1741
2216
|
* injected `loadOlder` action (disabled + `data-loading-older` while
|
|
1742
|
-
* `loadingOlder`), and a compact hint
|
|
2217
|
+
* `loadingOlder`), and a compact hint on the rail's free side states how many
|
|
1743
2218
|
* messages the current window covers.
|
|
2219
|
+
*
|
|
2220
|
+
* Settings (B-design): the gear is a REGULAR toolbar feature ("settings",
|
|
2221
|
+
* registry last, default unpinned) — the collapsed rail shows only the expand
|
|
2222
|
+
* arrow plus the user's pinned keys, and expanding reveals the gear at the end
|
|
2223
|
+
* of the queue. The gear opens a CENTERED modal dialog (function-key pins /
|
|
2224
|
+
* hover descriptions, the personalization controls, and the support-us card
|
|
2225
|
+
* grid); everything the modal changes persists under `dsh-milestone.toolbar`.
|
|
1744
2226
|
*/
|
|
1745
2227
|
/** Minimum user messages before the rail adds value. */
|
|
1746
2228
|
const MIN_MARKS = 2;
|
|
@@ -1749,17 +2231,40 @@ window.__ModuleLoader__.load({
|
|
|
1749
2231
|
const NO_BOOKMARKS = [];
|
|
1750
2232
|
/** Stable no-kinds fallback for marks whose turn carries no badge nodes. */
|
|
1751
2233
|
const NO_KINDS = [];
|
|
2234
|
+
/** Visual dot diameter at the default icon size (px). */
|
|
2235
|
+
const DOT_SIZE = 14;
|
|
2236
|
+
/** Hit area per dot at the default icon size (px) — larger than the dot. */
|
|
2237
|
+
const DOT_HIT = 28;
|
|
2238
|
+
/** Vertical gap between dot hit areas at the default icon size (px). */
|
|
2239
|
+
const DOT_GAP = 14;
|
|
1752
2240
|
/**
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
2241
|
+
* Extra top margin a new turn group's FIRST dot gets (replaces the old
|
|
2242
|
+
* `data-turn-separator` line): same-group pitch stays DOT_GAP, a group
|
|
2243
|
+
* boundary opens another GROUP_GAP_EXTRA px (14 → 18 at default size),
|
|
2244
|
+
* expressed purely as spacing — no line element.
|
|
1757
2245
|
*/
|
|
1758
|
-
const
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
2246
|
+
const GROUP_GAP_EXTRA = 4;
|
|
2247
|
+
/**
|
|
2248
|
+
* Preset accent swatches for the settings 强调色 row (default blue first).
|
|
2249
|
+
* The custom color input accepts any #rrggbb.
|
|
2250
|
+
*/
|
|
2251
|
+
const ACCENT_PRESETS = [
|
|
2252
|
+
"#4d7cfd",
|
|
2253
|
+
"#22c55e",
|
|
2254
|
+
"#f59e0b",
|
|
2255
|
+
"#ef4444",
|
|
2256
|
+
"#a855f7",
|
|
2257
|
+
"#06b6d4",
|
|
2258
|
+
"#ec4899",
|
|
2259
|
+
"#f97316"
|
|
2260
|
+
];
|
|
2261
|
+
/** Known floating-panel widths (px) used to anchor side=left panels to the
|
|
2262
|
+
* rail's free (right) side — the panel components take a viewport `right`
|
|
2263
|
+
* offset, so a left rail must back-calculate it from the panel width. */
|
|
2264
|
+
const PANEL_WIDTH_SEARCH = 220;
|
|
2265
|
+
const PANEL_WIDTH_STANDARD = 280;
|
|
2266
|
+
/** Tooltip anchor width: its maxWidth cap, so a tooltip never overlaps the rail. */
|
|
2267
|
+
const TOOLTIP_ANCHOR_WIDTH = 300;
|
|
1763
2268
|
/**
|
|
1764
2269
|
* P3 focus mode: dims the harness's AI thinking/scratchpad blocks so the
|
|
1765
2270
|
* conversation reads cleaner. The rule targets the stable, un-hashed
|
|
@@ -1767,18 +2272,33 @@ window.__ModuleLoader__.load({
|
|
|
1767
2272
|
* renders it as `data-variant="think"` with `data-state="running|ok"`), so an
|
|
1768
2273
|
* overlay plugin can dim it with plain CSS. Hovering a dimmed block (or
|
|
1769
2274
|
* opening it, `[data-open]`) restores full opacity. Kept in an inline
|
|
1770
|
-
* <style> so the plugin stays zero-asset
|
|
2275
|
+
* <style> so the plugin stays zero-asset.
|
|
1771
2276
|
*/
|
|
1772
2277
|
const FOCUS_CSS = `[data-variant="think"] { opacity: 0.4; transition: opacity 0.2s; }
|
|
1773
2278
|
[data-variant="think"]:hover, [data-variant="think"] [data-open] { opacity: 1; }`;
|
|
1774
|
-
/**
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
2279
|
+
/**
|
|
2280
|
+
* Static settings-modal styling that needs `:hover` (which inline styles
|
|
2281
|
+
* cannot express): the support-us card grid (micro-lift + accent highlight)
|
|
2282
|
+
* and the pin-row hover wash. Accent values come from the rail root's CSS
|
|
2283
|
+
* variables (`--ms-accent`), so one block serves every accent. The
|
|
2284
|
+
* search-toggle recolor rule makes the EXTERNAL RailSearchUi chrome follow
|
|
2285
|
+
* the accent too (that file is owned by an earlier phase and cannot change);
|
|
2286
|
+
* `!important` is required because the toggle's own inline styles win
|
|
2287
|
+
* otherwise.
|
|
2288
|
+
*/
|
|
2289
|
+
const MODAL_CSS = `
|
|
2290
|
+
[data-support-card] {
|
|
2291
|
+
display: flex; align-items: center; justify-content: center; gap: 8px;
|
|
2292
|
+
padding: 10px 12px; border-radius: 8px;
|
|
2293
|
+
background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.12);
|
|
2294
|
+
color: #c7cede; text-decoration: none; font-size: 12.5px; line-height: 1.4;
|
|
2295
|
+
transition: transform 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
2296
|
+
}
|
|
2297
|
+
[data-support-card]:hover { transform: translateY(-2px); border-color: var(--ms-accent); background: rgba(255, 255, 255, 0.09); }
|
|
2298
|
+
[data-toolbar-pin-toggle]:hover, [data-toolbar-pin-toggle]:focus-visible { background: rgba(255, 255, 255, 0.06); }
|
|
2299
|
+
[data-search-toggle] { color: #8b96ab !important; }
|
|
2300
|
+
[data-search-toggle][aria-pressed="true"] { background: var(--ms-accent-bg) !important; color: var(--ms-accent-soft) !important; }
|
|
2301
|
+
`;
|
|
1782
2302
|
/**
|
|
1783
2303
|
* P3 deep links (`#msg=<anchor-key>`): initial delay before the first
|
|
1784
2304
|
* deep-link attempt — the harness scrolls the conversation to the bottom on
|
|
@@ -1846,7 +2366,7 @@ window.__ModuleLoader__.load({
|
|
|
1846
2366
|
function MilestoneRail({ useSession, loadOlder, forkAt, useStore, actions, searchSessions = async () => ({
|
|
1847
2367
|
items: [],
|
|
1848
2368
|
hasMore: false
|
|
1849
|
-
}), openSession = () => {}, t = (key) => key }) {
|
|
2369
|
+
}), openSession = () => {}, t: frameworkT = (key) => key }) {
|
|
1850
2370
|
const order = useSession((s) => s.chat.order);
|
|
1851
2371
|
const nodes = useSession((s) => s.chat.nodes);
|
|
1852
2372
|
const locations = useSession((s) => s.chat.locations);
|
|
@@ -1951,46 +2471,57 @@ window.__ModuleLoader__.load({
|
|
|
1951
2471
|
}
|
|
1952
2472
|
return counts;
|
|
1953
2473
|
}, [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
|
-
|
|
2474
|
+
const [prefs, setPrefs] = (0, react.useState)(() => loadPrefs());
|
|
2475
|
+
const { pinned, accent, iconSize, inset, side } = prefs;
|
|
2476
|
+
const scale = iconSize / DOT_HIT;
|
|
2477
|
+
const hit = iconSize;
|
|
2478
|
+
const size = DOT_SIZE * scale;
|
|
2479
|
+
const gap = DOT_GAP * scale;
|
|
2480
|
+
const accentSoft = lighten(accent, .42) ?? "#9db8ff";
|
|
2481
|
+
const accentBg = rgbaString(accent, .18) ?? "rgba(77, 124, 254, 0.18)";
|
|
2482
|
+
const accentStrong = rgbaString(accent, .55) ?? "rgba(77, 124, 254, 0.55)";
|
|
2483
|
+
/**
|
|
2484
|
+
* Language override (settings → 语言): `system` delegates to the harness
|
|
2485
|
+
* `t` seat (the framework-synthesized interpreter for the registered
|
|
2486
|
+
* `dsh-milestone` namespace); `zh`/`en` force the plugin's own dictionaries
|
|
2487
|
+
* so the rail copy switches independently of the host UI language. Every
|
|
2488
|
+
* call site below — rail chrome, panels, tooltip and the settings modal —
|
|
2489
|
+
* already resolves through this binding, so the override is global to the
|
|
2490
|
+
* rail without threading a second translate prop anywhere.
|
|
2491
|
+
*/
|
|
2492
|
+
const t = prefs.locale === "system" ? frameworkT : prefs.locale === "en" ? (key, params) => translateDict(en, key, params) : (key, params) => translateDict(zh, key, params);
|
|
2493
|
+
/** Write a patch of prefs through to state + localStorage. */
|
|
2494
|
+
const updatePrefs = (patch) => {
|
|
2495
|
+
setPrefs((prev) => {
|
|
2496
|
+
const next = {
|
|
2497
|
+
...prev,
|
|
2498
|
+
...patch
|
|
2499
|
+
};
|
|
2500
|
+
savePrefs(next);
|
|
2501
|
+
return next;
|
|
2502
|
+
});
|
|
2503
|
+
};
|
|
2504
|
+
/** B1: flip one feature's pin — state and the persisted blob update together. */
|
|
2505
|
+
const onTogglePin = (id) => {
|
|
2506
|
+
setPrefs((prev) => {
|
|
2507
|
+
const next = togglePin(prev, id);
|
|
2508
|
+
savePrefs(next);
|
|
2509
|
+
return next;
|
|
2510
|
+
});
|
|
2511
|
+
};
|
|
2512
|
+
/** B-design: 恢复默认 resets EVERYTHING — pins AND personalization. */
|
|
2513
|
+
const onResetAll = () => {
|
|
2514
|
+
const next = { ...DEFAULT_PREFS };
|
|
2515
|
+
setPrefs(next);
|
|
2516
|
+
savePrefs(next);
|
|
2517
|
+
};
|
|
1991
2518
|
const [toolbarExpanded, setToolbarExpanded] = (0, react.useState)(false);
|
|
2519
|
+
const [expandHovered, setExpandHovered] = (0, react.useState)(false);
|
|
2520
|
+
const [settingsHovered, setSettingsHovered] = (0, react.useState)(false);
|
|
1992
2521
|
const [settingsOpen, setSettingsOpen] = (0, react.useState)(false);
|
|
1993
|
-
|
|
2522
|
+
/** The feature whose description the settings modal's right pane shows
|
|
2523
|
+
* (defaults to the first registry feature — 'search'). */
|
|
2524
|
+
const [descFeature, setDescFeature] = (0, react.useState)("search");
|
|
1994
2525
|
const settingsRef = (0, react.useRef)(null);
|
|
1995
2526
|
const settingsBtnRef = (0, react.useRef)(null);
|
|
1996
2527
|
const [updateOpen, setUpdateOpen] = (0, react.useState)(false);
|
|
@@ -1998,11 +2529,12 @@ window.__ModuleLoader__.load({
|
|
|
1998
2529
|
const updatePanelRef = (0, react.useRef)(null);
|
|
1999
2530
|
const updateBtnRef = (0, react.useRef)(null);
|
|
2000
2531
|
/**
|
|
2001
|
-
* B1 settings
|
|
2532
|
+
* B1 settings modal: outside-pointerdown dismisses it (shared
|
|
2002
2533
|
* useOutsideDismiss contract) with focus returning to the gear afterwards.
|
|
2003
|
-
* The
|
|
2004
|
-
*
|
|
2005
|
-
*
|
|
2534
|
+
* The modal's full-screen overlay wraps the dialog, so a pointerdown on the
|
|
2535
|
+
* backdrop (or anywhere outside the dialog) closes it; the gear's own click
|
|
2536
|
+
* keeps its flip semantics through a `[data-toolbar-settings]` exclusion —
|
|
2537
|
+
* pointerdown on an armed gear must not double-close.
|
|
2006
2538
|
*/
|
|
2007
2539
|
useOutsideDismiss(settingsRef, settingsOpen, () => {
|
|
2008
2540
|
setSettingsOpen(false);
|
|
@@ -2018,6 +2550,10 @@ window.__ModuleLoader__.load({
|
|
|
2018
2550
|
window.addEventListener("keydown", onKey);
|
|
2019
2551
|
return () => window.removeEventListener("keydown", onKey);
|
|
2020
2552
|
}, [settingsOpen]);
|
|
2553
|
+
(0, react.useEffect)(() => {
|
|
2554
|
+
if (!settingsOpen) return;
|
|
2555
|
+
(settingsRef.current?.querySelector("[data-toolbar-settings-close]"))?.focus();
|
|
2556
|
+
}, [settingsOpen]);
|
|
2021
2557
|
/**
|
|
2022
2558
|
* B4: run one update check. Cache-aware (`loadCachedLatest` reuses an
|
|
2023
2559
|
* unexpired cached result without any network traffic) and never throws:
|
|
@@ -2112,6 +2648,64 @@ window.__ModuleLoader__.load({
|
|
|
2112
2648
|
window.addEventListener("hashchange", onHashChange);
|
|
2113
2649
|
return () => window.removeEventListener("hashchange", onHashChange);
|
|
2114
2650
|
}, []);
|
|
2651
|
+
(0, react.useLayoutEffect)(() => {
|
|
2652
|
+
if (marks.length < MIN_MARKS) {
|
|
2653
|
+
setRailBox(null);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
const scrollport = document.querySelector("[data-conversation-scroll]");
|
|
2657
|
+
if (scrollport === null) return;
|
|
2658
|
+
const compute = () => {
|
|
2659
|
+
const sp = scrollport.getBoundingClientRect();
|
|
2660
|
+
setRailBox({
|
|
2661
|
+
top: sp.top,
|
|
2662
|
+
height: sp.height,
|
|
2663
|
+
right: Math.max(0, window.innerWidth - sp.right + inset),
|
|
2664
|
+
left: Math.max(0, sp.left + inset)
|
|
2665
|
+
});
|
|
2666
|
+
};
|
|
2667
|
+
compute();
|
|
2668
|
+
const observer = new ResizeObserver(compute);
|
|
2669
|
+
observer.observe(scrollport);
|
|
2670
|
+
window.addEventListener("resize", compute);
|
|
2671
|
+
return () => {
|
|
2672
|
+
observer.disconnect();
|
|
2673
|
+
window.removeEventListener("resize", compute);
|
|
2674
|
+
};
|
|
2675
|
+
}, [marks.length, inset]);
|
|
2676
|
+
(0, react.useLayoutEffect)(() => {
|
|
2677
|
+
setFocusIndex((f) => clampIndex(f, render.items.length));
|
|
2678
|
+
}, [render.items.length]);
|
|
2679
|
+
const lastBadge = (0, react.useMemo)(() => {
|
|
2680
|
+
if (displayMarks.length === 0) return null;
|
|
2681
|
+
const last = displayMarks[displayMarks.length - 1];
|
|
2682
|
+
return deriveBadge({
|
|
2683
|
+
nodeKinds: last.turn === void 0 ? NO_KINDS : kindsByTurn.get(last.turn) ?? NO_KINDS,
|
|
2684
|
+
lastMark: true,
|
|
2685
|
+
running,
|
|
2686
|
+
awaitingInput
|
|
2687
|
+
});
|
|
2688
|
+
}, [
|
|
2689
|
+
displayMarks,
|
|
2690
|
+
kindsByTurn,
|
|
2691
|
+
running,
|
|
2692
|
+
awaitingInput
|
|
2693
|
+
]);
|
|
2694
|
+
const pulseCss = (0, react.useMemo)(() => {
|
|
2695
|
+
if (lastBadge === null) return null;
|
|
2696
|
+
const style = badgeRingStyle(lastBadge);
|
|
2697
|
+
return style.pulse ? badgePulseCss(style.color) : null;
|
|
2698
|
+
}, [lastBadge]);
|
|
2699
|
+
(0, react.useEffect)(() => {
|
|
2700
|
+
if (!listOpen && !crossOpen) return;
|
|
2701
|
+
const onKey = (e) => {
|
|
2702
|
+
if (e.key !== "Escape") return;
|
|
2703
|
+
setListOpen(false);
|
|
2704
|
+
setCrossOpen(false);
|
|
2705
|
+
};
|
|
2706
|
+
window.addEventListener("keydown", onKey);
|
|
2707
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
2708
|
+
}, [listOpen, crossOpen]);
|
|
2115
2709
|
if (railBox === null || marks.length < MIN_MARKS) return null;
|
|
2116
2710
|
const updateQuery = (query) => {
|
|
2117
2711
|
setSearch({
|
|
@@ -2148,28 +2742,45 @@ window.__ModuleLoader__.load({
|
|
|
2148
2742
|
if (e.key === "Enter") advanceMatch();
|
|
2149
2743
|
if (e.key === "Escape") closeSearch();
|
|
2150
2744
|
};
|
|
2151
|
-
/**
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
/**
|
|
2160
|
-
const
|
|
2161
|
-
|
|
2162
|
-
|
|
2745
|
+
/**
|
|
2746
|
+
* B-design: viewport `right` offset for the floating layers. On the classic
|
|
2747
|
+
* right-side rail the panels sit left of the rail (their right edge at
|
|
2748
|
+
* railBox.right + hit + 8); on a LEFT rail every layer flips to the rail's
|
|
2749
|
+
* OTHER side — its left edge at railBox.left + hit + 8, which means its
|
|
2750
|
+
* viewport `right` must be backed out from the (known) panel width.
|
|
2751
|
+
*/
|
|
2752
|
+
const panelRightFor = (panelWidth) => side === "left" ? window.innerWidth - (railBox.left + hit + 8 + panelWidth) : railBox.right + hit + 8;
|
|
2753
|
+
/** Close the settings modal via its backdrop/close button paths. */
|
|
2754
|
+
const closeSettings = () => {
|
|
2755
|
+
setSettingsOpen(false);
|
|
2756
|
+
settingsBtnRef.current?.focus();
|
|
2163
2757
|
};
|
|
2164
2758
|
/** B1: a feature renders while the toolbar is EXPANDED or while it is pinned. */
|
|
2165
2759
|
const featureVisible = (id) => toolbarExpanded || pinned.includes(id);
|
|
2760
|
+
/** Base chrome-button style: accent-defined active tint, scaled hit area. */
|
|
2761
|
+
const chromeButtonStyle = (active) => ({
|
|
2762
|
+
width: hit,
|
|
2763
|
+
height: hit,
|
|
2764
|
+
flexShrink: 0,
|
|
2765
|
+
display: "flex",
|
|
2766
|
+
alignItems: "center",
|
|
2767
|
+
justifyContent: "center",
|
|
2768
|
+
background: active ? accentBg : "transparent",
|
|
2769
|
+
border: "none",
|
|
2770
|
+
padding: 0,
|
|
2771
|
+
cursor: "pointer",
|
|
2772
|
+
color: active ? accentSoft : "#8b96ab",
|
|
2773
|
+
transition: "background 120ms ease, color 120ms ease"
|
|
2774
|
+
});
|
|
2166
2775
|
/**
|
|
2167
2776
|
* B1: the data-driven feature registry. Each entry's render is the feature's
|
|
2168
|
-
*
|
|
2169
|
-
*
|
|
2170
|
-
*
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
2777
|
+
* rail-top chrome (data attributes / aria semantics preserved), moved
|
|
2778
|
+
* verbatim from the previous static button block; `search` is the whole
|
|
2779
|
+
* RailSearchUi (toggle + panel) so its lifecycle stays component-local in
|
|
2780
|
+
* the rail (search state lives in the rail and survives unmount). `settings`
|
|
2781
|
+
* lives LAST in the queue — the gear is a regular, default-unpinned feature;
|
|
2782
|
+
* the modal must stay reachable via 展开→齿轮.
|
|
2783
|
+
* Registry order = settings-menu order (站内搜索/全部提问/跨会话搜索/只看收藏/聚焦模式/检查更新/设置).
|
|
2173
2784
|
*
|
|
2174
2785
|
* EXTENSION POINT: push a new feature here (+ its id in toolbar-prefs.ts's
|
|
2175
2786
|
* TOOLBAR_PIN_IDS and its locale keys) and pinning/settings/expand all
|
|
@@ -2181,7 +2792,7 @@ window.__ModuleLoader__.load({
|
|
|
2181
2792
|
labelKey: "search.label",
|
|
2182
2793
|
render: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RailSearchUi, {
|
|
2183
2794
|
panelTop: railBox.top,
|
|
2184
|
-
panelRight:
|
|
2795
|
+
panelRight: panelRightFor(PANEL_WIDTH_SEARCH),
|
|
2185
2796
|
query: search.query,
|
|
2186
2797
|
panelOpen: search.panelOpen,
|
|
2187
2798
|
matches: matches.length,
|
|
@@ -2206,19 +2817,7 @@ window.__ModuleLoader__.load({
|
|
|
2206
2817
|
title: listOpen ? t("list.close") : t("list.open"),
|
|
2207
2818
|
"aria-pressed": listOpen,
|
|
2208
2819
|
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
|
-
},
|
|
2820
|
+
style: chromeButtonStyle(listOpen),
|
|
2222
2821
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2223
2822
|
width: "16",
|
|
2224
2823
|
height: "16",
|
|
@@ -2246,19 +2845,7 @@ window.__ModuleLoader__.load({
|
|
|
2246
2845
|
title: crossOpen ? t("search.cross.close") : t("search.cross.open"),
|
|
2247
2846
|
"aria-pressed": crossOpen,
|
|
2248
2847
|
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
|
-
},
|
|
2848
|
+
style: chromeButtonStyle(crossOpen),
|
|
2262
2849
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2263
2850
|
width: "16",
|
|
2264
2851
|
height: "16",
|
|
@@ -2293,19 +2880,7 @@ window.__ModuleLoader__.load({
|
|
|
2293
2880
|
"aria-pressed": bookmarksOnly,
|
|
2294
2881
|
"data-active": bookmarksOnly ? "true" : void 0,
|
|
2295
2882
|
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
|
-
},
|
|
2883
|
+
style: chromeButtonStyle(bookmarksOnly),
|
|
2309
2884
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2310
2885
|
width: "16",
|
|
2311
2886
|
height: "16",
|
|
@@ -2329,19 +2904,7 @@ window.__ModuleLoader__.load({
|
|
|
2329
2904
|
title: focusActive ? t("focus.off") : t("focus.on"),
|
|
2330
2905
|
"aria-pressed": focusActive,
|
|
2331
2906
|
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
|
-
},
|
|
2907
|
+
style: chromeButtonStyle(focusActive),
|
|
2345
2908
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2346
2909
|
width: "16",
|
|
2347
2910
|
height: "16",
|
|
@@ -2373,17 +2936,17 @@ window.__ModuleLoader__.load({
|
|
|
2373
2936
|
onClick: () => setUpdateOpen((v) => !v),
|
|
2374
2937
|
style: {
|
|
2375
2938
|
position: "relative",
|
|
2376
|
-
width:
|
|
2377
|
-
height:
|
|
2939
|
+
width: hit,
|
|
2940
|
+
height: hit,
|
|
2378
2941
|
flexShrink: 0,
|
|
2379
2942
|
display: "flex",
|
|
2380
2943
|
alignItems: "center",
|
|
2381
2944
|
justifyContent: "center",
|
|
2382
|
-
background:
|
|
2945
|
+
background: updateCheck.available ? "rgba(245, 197, 66, 0.14)" : updateOpen ? accentBg : "transparent",
|
|
2383
2946
|
border: "none",
|
|
2384
2947
|
padding: 0,
|
|
2385
2948
|
cursor: "pointer",
|
|
2386
|
-
color: updateCheck.available ? "#f5c542" : "#8b96ab"
|
|
2949
|
+
color: updateCheck.available ? "#f5c542" : updateOpen ? accentSoft : "#8b96ab"
|
|
2387
2950
|
},
|
|
2388
2951
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2389
2952
|
width: "16",
|
|
@@ -2416,6 +2979,40 @@ window.__ModuleLoader__.load({
|
|
|
2416
2979
|
}
|
|
2417
2980
|
})]
|
|
2418
2981
|
})
|
|
2982
|
+
},
|
|
2983
|
+
{
|
|
2984
|
+
id: "settings",
|
|
2985
|
+
labelKey: "settings.label",
|
|
2986
|
+
render: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2987
|
+
type: "button",
|
|
2988
|
+
ref: settingsBtnRef,
|
|
2989
|
+
"data-toolbar-settings": true,
|
|
2990
|
+
"aria-pressed": settingsOpen,
|
|
2991
|
+
"aria-label": settingsOpen ? t("toolbar.settings.close") : t("toolbar.settings.open"),
|
|
2992
|
+
title: settingsOpen ? t("toolbar.settings.close") : t("toolbar.settings.open"),
|
|
2993
|
+
onClick: () => setSettingsOpen((v) => !v),
|
|
2994
|
+
onMouseEnter: () => setSettingsHovered(true),
|
|
2995
|
+
onMouseLeave: () => setSettingsHovered(false),
|
|
2996
|
+
onFocus: () => setSettingsHovered(true),
|
|
2997
|
+
onBlur: () => setSettingsHovered(false),
|
|
2998
|
+
style: chromeButtonStyle(settingsOpen || settingsHovered),
|
|
2999
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
3000
|
+
width: "16",
|
|
3001
|
+
height: "16",
|
|
3002
|
+
viewBox: "0 0 24 24",
|
|
3003
|
+
fill: "none",
|
|
3004
|
+
stroke: "currentColor",
|
|
3005
|
+
strokeWidth: "2",
|
|
3006
|
+
strokeLinecap: "round",
|
|
3007
|
+
strokeLinejoin: "round",
|
|
3008
|
+
"aria-hidden": "true",
|
|
3009
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3010
|
+
cx: "12",
|
|
3011
|
+
cy: "12",
|
|
3012
|
+
r: "3"
|
|
3013
|
+
}), /* @__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" })]
|
|
3014
|
+
})
|
|
3015
|
+
})
|
|
2419
3016
|
}
|
|
2420
3017
|
];
|
|
2421
3018
|
/** Focus the dot at `index` (no-op while the list is unmounted). */
|
|
@@ -2533,25 +3130,37 @@ window.__ModuleLoader__.load({
|
|
|
2533
3130
|
forkAt(mark.seq).then(() => setForkedKey(mark.key));
|
|
2534
3131
|
};
|
|
2535
3132
|
const showLoadOlder = hasMore && marks.length >= MIN_MARKS;
|
|
3133
|
+
const railStyle = {
|
|
3134
|
+
position: "fixed",
|
|
3135
|
+
top: railBox.top,
|
|
3136
|
+
...side === "left" ? { left: railBox.left } : { right: railBox.right },
|
|
3137
|
+
height: railBox.height,
|
|
3138
|
+
width: hit,
|
|
3139
|
+
pointerEvents: "auto",
|
|
3140
|
+
zIndex: 100,
|
|
3141
|
+
display: "flex",
|
|
3142
|
+
flexDirection: "column",
|
|
3143
|
+
gap: 6,
|
|
3144
|
+
paddingTop: 6,
|
|
3145
|
+
"--ms-accent": accent,
|
|
3146
|
+
"--ms-accent-soft": accentSoft,
|
|
3147
|
+
"--ms-accent-bg": accentBg,
|
|
3148
|
+
"--ms-icon": `${iconSize}px`,
|
|
3149
|
+
"--ms-inset": `${inset}px`
|
|
3150
|
+
};
|
|
3151
|
+
const descKey = `settings.desc.${descFeature}`;
|
|
2536
3152
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2537
|
-
style:
|
|
2538
|
-
position: "fixed",
|
|
2539
|
-
top: railBox.top,
|
|
2540
|
-
right: railBox.right,
|
|
2541
|
-
height: railBox.height,
|
|
2542
|
-
width: DOT_HIT,
|
|
2543
|
-
pointerEvents: "auto",
|
|
2544
|
-
zIndex: 100,
|
|
2545
|
-
display: "flex",
|
|
2546
|
-
flexDirection: "column",
|
|
2547
|
-
gap: 6,
|
|
2548
|
-
paddingTop: 6
|
|
2549
|
-
},
|
|
3153
|
+
style: railStyle,
|
|
2550
3154
|
"aria-label": t("rail.label"),
|
|
2551
3155
|
"data-focus-active": focusActive ? "true" : void 0,
|
|
3156
|
+
"data-accent": accent,
|
|
3157
|
+
"data-side": side,
|
|
3158
|
+
"data-icon-size": String(iconSize),
|
|
3159
|
+
"data-inset": String(inset),
|
|
2552
3160
|
children: [
|
|
2553
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children:
|
|
3161
|
+
pulseCss !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: pulseCss }),
|
|
2554
3162
|
focusActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: FOCUS_CSS }),
|
|
3163
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: MODAL_CSS }),
|
|
2555
3164
|
showLoadOlder && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2556
3165
|
type: "button",
|
|
2557
3166
|
"data-load-older": true,
|
|
@@ -2563,8 +3172,8 @@ window.__ModuleLoader__.load({
|
|
|
2563
3172
|
loadOlder();
|
|
2564
3173
|
},
|
|
2565
3174
|
style: {
|
|
2566
|
-
width:
|
|
2567
|
-
height:
|
|
3175
|
+
width: hit,
|
|
3176
|
+
height: hit,
|
|
2568
3177
|
flexShrink: 0,
|
|
2569
3178
|
display: "flex",
|
|
2570
3179
|
alignItems: "center",
|
|
@@ -2587,19 +3196,11 @@ window.__ModuleLoader__.load({
|
|
|
2587
3196
|
"aria-label": toolbarExpanded ? t("toolbar.collapse") : t("toolbar.expand"),
|
|
2588
3197
|
title: toolbarExpanded ? t("toolbar.collapse") : t("toolbar.expand"),
|
|
2589
3198
|
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
|
-
},
|
|
3199
|
+
onMouseEnter: () => setExpandHovered(true),
|
|
3200
|
+
onMouseLeave: () => setExpandHovered(false),
|
|
3201
|
+
onFocus: () => setExpandHovered(true),
|
|
3202
|
+
onBlur: () => setExpandHovered(false),
|
|
3203
|
+
style: chromeButtonStyle(expandHovered),
|
|
2603
3204
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2604
3205
|
width: "16",
|
|
2605
3206
|
height: "16",
|
|
@@ -2613,221 +3214,576 @@ window.__ModuleLoader__.load({
|
|
|
2613
3214
|
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
3215
|
})
|
|
2615
3216
|
}),
|
|
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),
|
|
3217
|
+
toolbarFeatures.map((feature) => featureVisible(feature.id) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: feature.render() }, feature.id) : null),
|
|
3218
|
+
settingsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3219
|
+
"data-toolbar-settings-overlay": true,
|
|
3220
|
+
onClick: closeSettings,
|
|
2624
3221
|
style: {
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
3222
|
+
position: "fixed",
|
|
3223
|
+
inset: 0,
|
|
3224
|
+
background: "rgba(8, 10, 15, 0.55)",
|
|
3225
|
+
zIndex: 105,
|
|
2628
3226
|
display: "flex",
|
|
2629
3227
|
alignItems: "center",
|
|
2630
3228
|
justifyContent: "center",
|
|
2631
|
-
|
|
2632
|
-
border: "none",
|
|
2633
|
-
padding: 0,
|
|
2634
|
-
cursor: "pointer",
|
|
2635
|
-
color: settingsOpen ? "#9db8ff" : "#8b96ab"
|
|
3229
|
+
padding: 16
|
|
2636
3230
|
},
|
|
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
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2673
|
-
"data-toolbar-settings-title": true,
|
|
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", {
|
|
3231
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3232
|
+
ref: settingsRef,
|
|
3233
|
+
"data-toolbar-settings-panel": true,
|
|
3234
|
+
role: "dialog",
|
|
3235
|
+
"aria-modal": "true",
|
|
3236
|
+
"aria-label": t("settings.title"),
|
|
3237
|
+
onClick: (e) => e.stopPropagation(),
|
|
3238
|
+
style: {
|
|
3239
|
+
width: "min(600px, 92vw)",
|
|
3240
|
+
maxHeight: "78vh",
|
|
3241
|
+
overflowY: "auto",
|
|
3242
|
+
padding: "16px 18px",
|
|
3243
|
+
background: "rgba(20, 24, 32, 0.98)",
|
|
3244
|
+
color: "#e6e8ee",
|
|
3245
|
+
borderRadius: 12,
|
|
3246
|
+
boxShadow: "0 20px 60px rgba(0, 0, 0, 0.5)"
|
|
3247
|
+
},
|
|
3248
|
+
children: [
|
|
3249
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3250
|
+
style: {
|
|
3251
|
+
display: "flex",
|
|
3252
|
+
alignItems: "center",
|
|
3253
|
+
justifyContent: "space-between",
|
|
3254
|
+
gap: 8,
|
|
3255
|
+
marginBottom: 12
|
|
3256
|
+
},
|
|
3257
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3258
|
+
"data-toolbar-settings-title": true,
|
|
3259
|
+
style: {
|
|
3260
|
+
fontSize: 15,
|
|
3261
|
+
fontWeight: 600,
|
|
3262
|
+
color: "#e6e8ee"
|
|
3263
|
+
},
|
|
3264
|
+
children: t("settings.title")
|
|
3265
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2702
3266
|
type: "button",
|
|
2703
|
-
|
|
2704
|
-
"
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
onClick: () => onTogglePin(feature.id),
|
|
3267
|
+
"data-toolbar-settings-close": true,
|
|
3268
|
+
"aria-label": t("settings.close"),
|
|
3269
|
+
title: t("settings.close"),
|
|
3270
|
+
onClick: closeSettings,
|
|
2708
3271
|
style: {
|
|
3272
|
+
width: 26,
|
|
3273
|
+
height: 26,
|
|
3274
|
+
flexShrink: 0,
|
|
2709
3275
|
display: "flex",
|
|
2710
3276
|
alignItems: "center",
|
|
2711
|
-
justifyContent: "
|
|
2712
|
-
gap: 8,
|
|
2713
|
-
width: "100%",
|
|
2714
|
-
padding: "6px 8px",
|
|
3277
|
+
justifyContent: "center",
|
|
2715
3278
|
background: "transparent",
|
|
2716
3279
|
border: "none",
|
|
2717
|
-
|
|
3280
|
+
padding: 0,
|
|
2718
3281
|
cursor: "pointer",
|
|
2719
|
-
color: "#
|
|
3282
|
+
color: "#8b96ab",
|
|
3283
|
+
borderRadius: 6,
|
|
3284
|
+
fontSize: 14,
|
|
3285
|
+
lineHeight: 1
|
|
3286
|
+
},
|
|
3287
|
+
children: "✕"
|
|
3288
|
+
})]
|
|
3289
|
+
}),
|
|
3290
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3291
|
+
"data-settings-section": true,
|
|
3292
|
+
style: { marginBottom: 14 },
|
|
3293
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3294
|
+
"data-settings-section-title": true,
|
|
3295
|
+
style: {
|
|
2720
3296
|
fontSize: 13,
|
|
2721
|
-
|
|
3297
|
+
fontWeight: 600,
|
|
3298
|
+
color: "#c7cede",
|
|
3299
|
+
marginBottom: 8
|
|
3300
|
+
},
|
|
3301
|
+
children: t("settings.section.features")
|
|
3302
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3303
|
+
style: {
|
|
3304
|
+
display: "grid",
|
|
3305
|
+
gridTemplateColumns: "minmax(0, 1fr) 190px",
|
|
3306
|
+
gap: 12,
|
|
3307
|
+
alignItems: "stretch"
|
|
2722
3308
|
},
|
|
2723
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2724
|
-
"aria-hidden": "true",
|
|
3309
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2725
3310
|
style: {
|
|
2726
|
-
width: 14,
|
|
2727
|
-
height: 14,
|
|
2728
|
-
flexShrink: 0,
|
|
2729
|
-
borderRadius: 3,
|
|
2730
|
-
border: "1px solid rgba(255, 255, 255, 0.35)",
|
|
2731
|
-
background: checked ? "rgba(77, 124, 254, 0.9)" : "transparent",
|
|
2732
3311
|
display: "flex",
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
color: "#ffffff",
|
|
2736
|
-
fontSize: 10,
|
|
2737
|
-
lineHeight: 1
|
|
3312
|
+
flexDirection: "column",
|
|
3313
|
+
gap: 2
|
|
2738
3314
|
},
|
|
2739
|
-
children:
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
}),
|
|
2794
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2795
|
-
href: "https://github.com/SnowCrescenter-tech/dsh-milestone",
|
|
2796
|
-
target: "_blank",
|
|
2797
|
-
rel: "noreferrer",
|
|
3315
|
+
children: toolbarFeatures.map((feature) => {
|
|
3316
|
+
const checked = pinned.includes(feature.id);
|
|
3317
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3318
|
+
type: "button",
|
|
3319
|
+
role: "switch",
|
|
3320
|
+
"data-toolbar-pin-toggle": true,
|
|
3321
|
+
"data-pin-id": feature.id,
|
|
3322
|
+
"aria-checked": checked,
|
|
3323
|
+
"aria-label": t(feature.labelKey),
|
|
3324
|
+
onMouseEnter: () => setDescFeature(feature.id),
|
|
3325
|
+
onFocus: () => setDescFeature(feature.id),
|
|
3326
|
+
onClick: () => onTogglePin(feature.id),
|
|
3327
|
+
style: {
|
|
3328
|
+
display: "flex",
|
|
3329
|
+
alignItems: "center",
|
|
3330
|
+
justifyContent: "space-between",
|
|
3331
|
+
gap: 10,
|
|
3332
|
+
width: "100%",
|
|
3333
|
+
padding: "7px 10px",
|
|
3334
|
+
background: "transparent",
|
|
3335
|
+
border: "none",
|
|
3336
|
+
borderRadius: 8,
|
|
3337
|
+
cursor: "pointer",
|
|
3338
|
+
color: "#e6e8ee",
|
|
3339
|
+
fontSize: 13,
|
|
3340
|
+
textAlign: "left"
|
|
3341
|
+
},
|
|
3342
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(feature.labelKey) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3343
|
+
"aria-hidden": "true",
|
|
3344
|
+
style: {
|
|
3345
|
+
position: "relative",
|
|
3346
|
+
width: 32,
|
|
3347
|
+
height: 18,
|
|
3348
|
+
flexShrink: 0,
|
|
3349
|
+
borderRadius: 9,
|
|
3350
|
+
background: checked ? accentStrong : "rgba(255, 255, 255, 0.16)",
|
|
3351
|
+
transition: "background 120ms ease"
|
|
3352
|
+
},
|
|
3353
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
3354
|
+
position: "absolute",
|
|
3355
|
+
top: 2,
|
|
3356
|
+
left: checked ? 16 : 2,
|
|
3357
|
+
width: 14,
|
|
3358
|
+
height: 14,
|
|
3359
|
+
borderRadius: "50%",
|
|
3360
|
+
background: "#ffffff",
|
|
3361
|
+
transition: "left 120ms ease"
|
|
3362
|
+
} })
|
|
3363
|
+
})]
|
|
3364
|
+
}, feature.id);
|
|
3365
|
+
})
|
|
3366
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3367
|
+
"data-settings-desc": true,
|
|
3368
|
+
"aria-live": "polite",
|
|
2798
3369
|
style: {
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
target: "_blank",
|
|
2808
|
-
rel: "noreferrer",
|
|
2809
|
-
style: {
|
|
2810
|
-
fontSize: 12,
|
|
2811
|
-
color: "#9db8ff",
|
|
2812
|
-
textDecoration: "none"
|
|
3370
|
+
display: "flex",
|
|
3371
|
+
alignItems: "center",
|
|
3372
|
+
padding: "10px 12px",
|
|
3373
|
+
borderRadius: 8,
|
|
3374
|
+
background: "rgba(255, 255, 255, 0.05)",
|
|
3375
|
+
color: "#b9c2d4",
|
|
3376
|
+
fontSize: 12.5,
|
|
3377
|
+
lineHeight: 1.5
|
|
2813
3378
|
},
|
|
2814
|
-
children: t(
|
|
2815
|
-
})
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
3379
|
+
children: t(descKey)
|
|
3380
|
+
})]
|
|
3381
|
+
})]
|
|
3382
|
+
}),
|
|
3383
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3384
|
+
"data-settings-section": true,
|
|
3385
|
+
"data-settings-personal": true,
|
|
3386
|
+
style: { marginBottom: 14 },
|
|
3387
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3388
|
+
"data-settings-section-title": true,
|
|
3389
|
+
style: {
|
|
3390
|
+
fontSize: 13,
|
|
3391
|
+
fontWeight: 600,
|
|
3392
|
+
color: "#c7cede",
|
|
3393
|
+
marginBottom: 8
|
|
3394
|
+
},
|
|
3395
|
+
children: t("settings.section.personal")
|
|
3396
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3397
|
+
style: {
|
|
3398
|
+
display: "flex",
|
|
3399
|
+
flexDirection: "column",
|
|
3400
|
+
gap: 10
|
|
3401
|
+
},
|
|
3402
|
+
children: [
|
|
3403
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3404
|
+
style: {
|
|
3405
|
+
display: "flex",
|
|
3406
|
+
alignItems: "center",
|
|
3407
|
+
gap: 10,
|
|
3408
|
+
flexWrap: "wrap"
|
|
3409
|
+
},
|
|
3410
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3411
|
+
style: {
|
|
3412
|
+
fontSize: 12.5,
|
|
3413
|
+
color: "#8b96ab",
|
|
3414
|
+
width: 90,
|
|
3415
|
+
flexShrink: 0
|
|
3416
|
+
},
|
|
3417
|
+
children: t("settings.accent")
|
|
3418
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3419
|
+
"data-accent-swatches": true,
|
|
3420
|
+
style: {
|
|
3421
|
+
display: "flex",
|
|
3422
|
+
alignItems: "center",
|
|
3423
|
+
gap: 6,
|
|
3424
|
+
flexWrap: "wrap"
|
|
3425
|
+
},
|
|
3426
|
+
children: [ACCENT_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3427
|
+
type: "button",
|
|
3428
|
+
"data-accent-swatch": true,
|
|
3429
|
+
"data-accent": preset,
|
|
3430
|
+
"aria-label": preset,
|
|
3431
|
+
"aria-pressed": accent === preset,
|
|
3432
|
+
onClick: () => updatePrefs({ accent: preset }),
|
|
3433
|
+
style: {
|
|
3434
|
+
width: 22,
|
|
3435
|
+
height: 22,
|
|
3436
|
+
borderRadius: "50%",
|
|
3437
|
+
background: preset,
|
|
3438
|
+
border: accent === preset ? "2px solid #ffffff" : "2px solid rgba(255, 255, 255, 0.25)",
|
|
3439
|
+
boxShadow: accent === preset ? `0 0 0 2px ${preset}` : "none",
|
|
3440
|
+
padding: 0,
|
|
3441
|
+
cursor: "pointer",
|
|
3442
|
+
transition: "border-color 120ms ease, box-shadow 120ms ease"
|
|
3443
|
+
}
|
|
3444
|
+
}, preset)), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3445
|
+
"data-accent-custom": true,
|
|
3446
|
+
style: {
|
|
3447
|
+
display: "inline-flex",
|
|
3448
|
+
alignItems: "center",
|
|
3449
|
+
gap: 6,
|
|
3450
|
+
fontSize: 12.5,
|
|
3451
|
+
color: "#b9c2d4",
|
|
3452
|
+
cursor: "pointer"
|
|
3453
|
+
},
|
|
3454
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3455
|
+
type: "color",
|
|
3456
|
+
value: accent,
|
|
3457
|
+
onChange: (e) => updatePrefs({ accent: e.target.value }),
|
|
3458
|
+
"aria-label": `${t("settings.custom")} ${t("settings.accent")}`,
|
|
3459
|
+
style: {
|
|
3460
|
+
width: 26,
|
|
3461
|
+
height: 26,
|
|
3462
|
+
padding: 0,
|
|
3463
|
+
border: "none",
|
|
3464
|
+
background: "transparent",
|
|
3465
|
+
cursor: "pointer"
|
|
3466
|
+
}
|
|
3467
|
+
}), t("settings.custom")]
|
|
3468
|
+
})]
|
|
3469
|
+
})]
|
|
3470
|
+
}),
|
|
3471
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3472
|
+
style: {
|
|
3473
|
+
display: "flex",
|
|
3474
|
+
alignItems: "center",
|
|
3475
|
+
gap: 10,
|
|
3476
|
+
flexWrap: "wrap"
|
|
3477
|
+
},
|
|
3478
|
+
children: [
|
|
3479
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3480
|
+
style: {
|
|
3481
|
+
fontSize: 12.5,
|
|
3482
|
+
color: "#8b96ab",
|
|
3483
|
+
width: 90,
|
|
3484
|
+
flexShrink: 0
|
|
3485
|
+
},
|
|
3486
|
+
children: t("settings.iconSize")
|
|
3487
|
+
}),
|
|
3488
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3489
|
+
type: "range",
|
|
3490
|
+
"data-icon-size": true,
|
|
3491
|
+
min: 20,
|
|
3492
|
+
max: 36,
|
|
3493
|
+
step: 2,
|
|
3494
|
+
value: iconSize,
|
|
3495
|
+
onChange: (e) => updatePrefs({ iconSize: Number(e.target.value) }),
|
|
3496
|
+
style: {
|
|
3497
|
+
flex: 1,
|
|
3498
|
+
minWidth: 140,
|
|
3499
|
+
maxWidth: 260
|
|
3500
|
+
}
|
|
3501
|
+
}),
|
|
3502
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3503
|
+
"data-icon-size-value": true,
|
|
3504
|
+
style: {
|
|
3505
|
+
fontSize: 12.5,
|
|
3506
|
+
color: "#b9c2d4",
|
|
3507
|
+
width: 40
|
|
3508
|
+
},
|
|
3509
|
+
children: [iconSize, "px"]
|
|
3510
|
+
})
|
|
3511
|
+
]
|
|
3512
|
+
}),
|
|
3513
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3514
|
+
style: {
|
|
3515
|
+
display: "flex",
|
|
3516
|
+
alignItems: "center",
|
|
3517
|
+
gap: 10,
|
|
3518
|
+
flexWrap: "wrap"
|
|
3519
|
+
},
|
|
3520
|
+
children: [
|
|
3521
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3522
|
+
style: {
|
|
3523
|
+
fontSize: 12.5,
|
|
3524
|
+
color: "#8b96ab",
|
|
3525
|
+
width: 90,
|
|
3526
|
+
flexShrink: 0
|
|
3527
|
+
},
|
|
3528
|
+
children: t("settings.inset")
|
|
3529
|
+
}),
|
|
3530
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3531
|
+
type: "range",
|
|
3532
|
+
"data-inset": true,
|
|
3533
|
+
min: 0,
|
|
3534
|
+
max: 40,
|
|
3535
|
+
step: 2,
|
|
3536
|
+
value: inset,
|
|
3537
|
+
onChange: (e) => updatePrefs({ inset: Number(e.target.value) }),
|
|
3538
|
+
style: {
|
|
3539
|
+
flex: 1,
|
|
3540
|
+
minWidth: 140,
|
|
3541
|
+
maxWidth: 260
|
|
3542
|
+
}
|
|
3543
|
+
}),
|
|
3544
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3545
|
+
"data-inset-value": true,
|
|
3546
|
+
style: {
|
|
3547
|
+
fontSize: 12.5,
|
|
3548
|
+
color: "#b9c2d4",
|
|
3549
|
+
width: 40
|
|
3550
|
+
},
|
|
3551
|
+
children: [inset, "px"]
|
|
3552
|
+
})
|
|
3553
|
+
]
|
|
3554
|
+
}),
|
|
3555
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3556
|
+
role: "radiogroup",
|
|
3557
|
+
"aria-label": t("settings.side"),
|
|
3558
|
+
style: {
|
|
3559
|
+
display: "flex",
|
|
3560
|
+
alignItems: "center",
|
|
3561
|
+
gap: 10,
|
|
3562
|
+
flexWrap: "wrap"
|
|
3563
|
+
},
|
|
3564
|
+
children: [
|
|
3565
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3566
|
+
style: {
|
|
3567
|
+
fontSize: 12.5,
|
|
3568
|
+
color: "#8b96ab",
|
|
3569
|
+
width: 90,
|
|
3570
|
+
flexShrink: 0
|
|
3571
|
+
},
|
|
3572
|
+
children: t("settings.side")
|
|
3573
|
+
}),
|
|
3574
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3575
|
+
style: {
|
|
3576
|
+
display: "inline-flex",
|
|
3577
|
+
alignItems: "center",
|
|
3578
|
+
gap: 5,
|
|
3579
|
+
fontSize: 13,
|
|
3580
|
+
color: "#e6e8ee",
|
|
3581
|
+
cursor: "pointer"
|
|
3582
|
+
},
|
|
3583
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3584
|
+
type: "radio",
|
|
3585
|
+
name: "ms-rail-side",
|
|
3586
|
+
"data-side-radio": true,
|
|
3587
|
+
value: "left",
|
|
3588
|
+
checked: side === "left",
|
|
3589
|
+
onChange: () => updatePrefs({ side: "left" })
|
|
3590
|
+
}), t("settings.side.left")]
|
|
3591
|
+
}),
|
|
3592
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
3593
|
+
style: {
|
|
3594
|
+
display: "inline-flex",
|
|
3595
|
+
alignItems: "center",
|
|
3596
|
+
gap: 5,
|
|
3597
|
+
fontSize: 13,
|
|
3598
|
+
color: "#e6e8ee",
|
|
3599
|
+
cursor: "pointer"
|
|
3600
|
+
},
|
|
3601
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3602
|
+
type: "radio",
|
|
3603
|
+
name: "ms-rail-side",
|
|
3604
|
+
"data-side-radio": true,
|
|
3605
|
+
value: "right",
|
|
3606
|
+
checked: side === "right",
|
|
3607
|
+
onChange: () => updatePrefs({ side: "right" })
|
|
3608
|
+
}), t("settings.side.right")]
|
|
3609
|
+
})
|
|
3610
|
+
]
|
|
3611
|
+
})
|
|
3612
|
+
]
|
|
3613
|
+
})]
|
|
3614
|
+
}),
|
|
3615
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3616
|
+
"data-settings-section": true,
|
|
3617
|
+
"data-settings-lang": true,
|
|
3618
|
+
style: { marginBottom: 14 },
|
|
3619
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3620
|
+
"data-settings-section-title": true,
|
|
3621
|
+
style: {
|
|
3622
|
+
fontSize: 13,
|
|
3623
|
+
fontWeight: 600,
|
|
3624
|
+
color: "#c7cede",
|
|
3625
|
+
marginBottom: 8
|
|
3626
|
+
},
|
|
3627
|
+
children: t("settings.language")
|
|
3628
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3629
|
+
role: "radiogroup",
|
|
3630
|
+
"aria-label": t("settings.language"),
|
|
3631
|
+
style: {
|
|
3632
|
+
display: "flex",
|
|
3633
|
+
alignItems: "center",
|
|
3634
|
+
gap: 18,
|
|
3635
|
+
flexWrap: "wrap"
|
|
3636
|
+
},
|
|
3637
|
+
children: [
|
|
3638
|
+
"system",
|
|
3639
|
+
"zh",
|
|
3640
|
+
"en"
|
|
3641
|
+
].map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2820
3642
|
style: {
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
3643
|
+
display: "inline-flex",
|
|
3644
|
+
alignItems: "center",
|
|
3645
|
+
gap: 5,
|
|
3646
|
+
fontSize: 13,
|
|
3647
|
+
color: "#e6e8ee",
|
|
3648
|
+
cursor: "pointer"
|
|
2824
3649
|
},
|
|
2825
|
-
children:
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
3650
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3651
|
+
type: "radio",
|
|
3652
|
+
name: "ms-rail-locale",
|
|
3653
|
+
"data-locale-pref": true,
|
|
3654
|
+
value,
|
|
3655
|
+
checked: prefs.locale === value,
|
|
3656
|
+
onChange: () => updatePrefs({ locale: value })
|
|
3657
|
+
}), t(`settings.lang.${value}`)]
|
|
3658
|
+
}, value))
|
|
3659
|
+
})]
|
|
3660
|
+
}),
|
|
3661
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3662
|
+
"data-toolbar-settings-footer": true,
|
|
3663
|
+
style: { textAlign: "center" },
|
|
3664
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3665
|
+
style: {
|
|
3666
|
+
fontSize: 12,
|
|
3667
|
+
color: "#8b96ab",
|
|
3668
|
+
marginBottom: 10
|
|
3669
|
+
},
|
|
3670
|
+
children: t("settings.support")
|
|
3671
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3672
|
+
"data-support-grid": true,
|
|
3673
|
+
style: {
|
|
3674
|
+
display: "grid",
|
|
3675
|
+
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
|
3676
|
+
gap: 10,
|
|
3677
|
+
maxWidth: 460,
|
|
3678
|
+
margin: "0 auto"
|
|
3679
|
+
},
|
|
3680
|
+
children: [
|
|
3681
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
3682
|
+
href: "https://github.com/SnowCrescenter-tech/dsh-milestone",
|
|
3683
|
+
target: "_blank",
|
|
3684
|
+
rel: "noreferrer",
|
|
3685
|
+
"data-support-card": true,
|
|
3686
|
+
"data-card": "repo",
|
|
3687
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3688
|
+
width: "14",
|
|
3689
|
+
height: "14",
|
|
3690
|
+
viewBox: "0 0 24 24",
|
|
3691
|
+
fill: "none",
|
|
3692
|
+
stroke: "currentColor",
|
|
3693
|
+
strokeWidth: "2",
|
|
3694
|
+
strokeLinecap: "round",
|
|
3695
|
+
strokeLinejoin: "round",
|
|
3696
|
+
"aria-hidden": "true",
|
|
3697
|
+
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" })
|
|
3698
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.repo") })]
|
|
3699
|
+
}),
|
|
3700
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
3701
|
+
href: "https://github.com/SnowCrescenter-tech/dsh-milestone",
|
|
3702
|
+
target: "_blank",
|
|
3703
|
+
rel: "noreferrer",
|
|
3704
|
+
"data-support-card": true,
|
|
3705
|
+
"data-card": "star",
|
|
3706
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3707
|
+
width: "14",
|
|
3708
|
+
height: "14",
|
|
3709
|
+
viewBox: "0 0 24 24",
|
|
3710
|
+
fill: "currentColor",
|
|
3711
|
+
stroke: "none",
|
|
3712
|
+
"aria-hidden": "true",
|
|
3713
|
+
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" })
|
|
3714
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.star") })]
|
|
3715
|
+
}),
|
|
3716
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
3717
|
+
href: `https://github.com/SnowCrescenter-tech/dsh-milestone/issues`,
|
|
3718
|
+
target: "_blank",
|
|
3719
|
+
rel: "noreferrer",
|
|
3720
|
+
"data-support-card": true,
|
|
3721
|
+
"data-card": "issues",
|
|
3722
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
3723
|
+
width: "14",
|
|
3724
|
+
height: "14",
|
|
3725
|
+
viewBox: "0 0 24 24",
|
|
3726
|
+
fill: "none",
|
|
3727
|
+
stroke: "currentColor",
|
|
3728
|
+
strokeWidth: "2",
|
|
3729
|
+
"aria-hidden": "true",
|
|
3730
|
+
children: [
|
|
3731
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3732
|
+
cx: "12",
|
|
3733
|
+
cy: "12",
|
|
3734
|
+
r: "9"
|
|
3735
|
+
}),
|
|
3736
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
3737
|
+
d: "M12 8v4",
|
|
3738
|
+
strokeLinecap: "round"
|
|
3739
|
+
}),
|
|
3740
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3741
|
+
cx: "12",
|
|
3742
|
+
cy: "16",
|
|
3743
|
+
r: "0.5",
|
|
3744
|
+
fill: "currentColor"
|
|
3745
|
+
})
|
|
3746
|
+
]
|
|
3747
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.issues") })]
|
|
3748
|
+
}),
|
|
3749
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
3750
|
+
href: "https://www.npmjs.com/package/dsh-milestone",
|
|
3751
|
+
target: "_blank",
|
|
3752
|
+
rel: "noreferrer",
|
|
3753
|
+
"data-support-card": true,
|
|
3754
|
+
"data-card": "npm",
|
|
3755
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3756
|
+
width: "14",
|
|
3757
|
+
height: "14",
|
|
3758
|
+
viewBox: "0 0 24 24",
|
|
3759
|
+
fill: "currentColor",
|
|
3760
|
+
stroke: "none",
|
|
3761
|
+
"aria-hidden": "true",
|
|
3762
|
+
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" })
|
|
3763
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("settings.npm") })]
|
|
3764
|
+
})
|
|
3765
|
+
]
|
|
3766
|
+
})]
|
|
3767
|
+
}),
|
|
3768
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3769
|
+
type: "button",
|
|
3770
|
+
"data-toolbar-settings-reset": true,
|
|
3771
|
+
onClick: onResetAll,
|
|
3772
|
+
style: {
|
|
3773
|
+
display: "block",
|
|
3774
|
+
margin: "14px auto 0",
|
|
3775
|
+
padding: "7px 16px",
|
|
3776
|
+
background: "rgba(255, 255, 255, 0.06)",
|
|
3777
|
+
border: "1px solid rgba(255, 255, 255, 0.14)",
|
|
3778
|
+
borderRadius: 8,
|
|
3779
|
+
cursor: "pointer",
|
|
3780
|
+
color: "#b9c2d4",
|
|
3781
|
+
fontSize: 12.5
|
|
3782
|
+
},
|
|
3783
|
+
children: t("settings.reset")
|
|
3784
|
+
})
|
|
3785
|
+
]
|
|
3786
|
+
})
|
|
2831
3787
|
}),
|
|
2832
3788
|
updateOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2833
3789
|
ref: updatePanelRef,
|
|
@@ -2835,7 +3791,7 @@ window.__ModuleLoader__.load({
|
|
|
2835
3791
|
style: {
|
|
2836
3792
|
position: "fixed",
|
|
2837
3793
|
top: railBox.top,
|
|
2838
|
-
right:
|
|
3794
|
+
right: panelRightFor(PANEL_WIDTH_STANDARD),
|
|
2839
3795
|
width: "min(280px, calc(100vw - 48px))",
|
|
2840
3796
|
padding: "10px 12px",
|
|
2841
3797
|
background: "rgba(20, 24, 32, 0.97)",
|
|
@@ -2865,7 +3821,7 @@ window.__ModuleLoader__.load({
|
|
|
2865
3821
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2866
3822
|
style: { color: "#8b96ab" },
|
|
2867
3823
|
children: [t("update.current"), ": "]
|
|
2868
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "0.6.
|
|
3824
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "0.6.2" })] }),
|
|
2869
3825
|
updateCheck.phase === "ok" && updateCheck.latest !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2870
3826
|
"data-update-latest": true,
|
|
2871
3827
|
children: [
|
|
@@ -2908,7 +3864,7 @@ window.__ModuleLoader__.load({
|
|
|
2908
3864
|
border: "none",
|
|
2909
3865
|
padding: 0,
|
|
2910
3866
|
cursor: "pointer",
|
|
2911
|
-
color:
|
|
3867
|
+
color: accentSoft,
|
|
2912
3868
|
fontSize: 12,
|
|
2913
3869
|
textDecoration: "underline"
|
|
2914
3870
|
},
|
|
@@ -2928,7 +3884,7 @@ window.__ModuleLoader__.load({
|
|
|
2928
3884
|
target: "_blank",
|
|
2929
3885
|
rel: "noreferrer",
|
|
2930
3886
|
style: {
|
|
2931
|
-
color:
|
|
3887
|
+
color: accentSoft,
|
|
2932
3888
|
textDecoration: "none"
|
|
2933
3889
|
},
|
|
2934
3890
|
children: t("update.goNpm")
|
|
@@ -2952,11 +3908,11 @@ window.__ModuleLoader__.load({
|
|
|
2952
3908
|
style: {
|
|
2953
3909
|
marginTop: 4,
|
|
2954
3910
|
padding: "6px 10px",
|
|
2955
|
-
background: updateCheck.phase === "checking" ? "transparent" :
|
|
3911
|
+
background: updateCheck.phase === "checking" ? "transparent" : accentBg,
|
|
2956
3912
|
border: "none",
|
|
2957
3913
|
borderRadius: 6,
|
|
2958
3914
|
cursor: updateCheck.phase === "checking" ? "default" : "pointer",
|
|
2959
|
-
color: updateCheck.phase === "checking" ? "#5a6375" :
|
|
3915
|
+
color: updateCheck.phase === "checking" ? "#5a6375" : accentSoft,
|
|
2960
3916
|
fontSize: 12,
|
|
2961
3917
|
alignSelf: "flex-start"
|
|
2962
3918
|
},
|
|
@@ -2967,14 +3923,14 @@ window.__ModuleLoader__.load({
|
|
|
2967
3923
|
}),
|
|
2968
3924
|
listOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MilestoneListPanel, {
|
|
2969
3925
|
panelTop: railBox.top,
|
|
2970
|
-
panelRight:
|
|
3926
|
+
panelRight: panelRightFor(PANEL_WIDTH_STANDARD),
|
|
2971
3927
|
marks,
|
|
2972
3928
|
onJump: jump,
|
|
2973
3929
|
t
|
|
2974
3930
|
}),
|
|
2975
3931
|
crossOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MilestoneSessionSearch, {
|
|
2976
3932
|
panelTop: railBox.top,
|
|
2977
|
-
panelRight:
|
|
3933
|
+
panelRight: panelRightFor(PANEL_WIDTH_STANDARD),
|
|
2978
3934
|
onClose: () => setCrossOpen(false),
|
|
2979
3935
|
searchSessions,
|
|
2980
3936
|
openSession,
|
|
@@ -2994,12 +3950,12 @@ window.__ModuleLoader__.load({
|
|
|
2994
3950
|
display: "flex",
|
|
2995
3951
|
flexDirection: "column",
|
|
2996
3952
|
alignItems: "center",
|
|
2997
|
-
gap
|
|
3953
|
+
gap,
|
|
2998
3954
|
padding: "6px 0",
|
|
2999
3955
|
scrollbarWidth: "none"
|
|
3000
3956
|
},
|
|
3001
3957
|
children: render.items.map((item, i) => {
|
|
3002
|
-
const
|
|
3958
|
+
const showGroupGap = separatorIndices.has(i) && i > 0;
|
|
3003
3959
|
const mark = displayMarks[item.displayIndex];
|
|
3004
3960
|
const summaryCount = collapsedSummaries.get(mark.key);
|
|
3005
3961
|
const bookmarked = isBookmarked(bookmarkedKeys, mark.key);
|
|
@@ -3011,7 +3967,7 @@ window.__ModuleLoader__.load({
|
|
|
3011
3967
|
isCurrent: !hasQuery && mark.key === currentKey
|
|
3012
3968
|
});
|
|
3013
3969
|
const isHovered = hover?.mark.key === mark.key;
|
|
3014
|
-
const boxShadow = isHovered ?
|
|
3970
|
+
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
3971
|
const badge = deriveBadge({
|
|
3016
3972
|
nodeKinds: mark.turn === void 0 ? NO_KINDS : kindsByTurn.get(mark.turn) ?? NO_KINDS,
|
|
3017
3973
|
lastMark: item.displayIndex === displayMarks.length - 1,
|
|
@@ -3019,21 +3975,11 @@ window.__ModuleLoader__.load({
|
|
|
3019
3975
|
awaitingInput
|
|
3020
3976
|
});
|
|
3021
3977
|
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", {
|
|
3978
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3033
3979
|
type: "button",
|
|
3034
3980
|
style: {
|
|
3035
|
-
width:
|
|
3036
|
-
height:
|
|
3981
|
+
width: hit,
|
|
3982
|
+
height: hit,
|
|
3037
3983
|
flexShrink: 0,
|
|
3038
3984
|
display: "flex",
|
|
3039
3985
|
alignItems: "center",
|
|
@@ -3041,7 +3987,8 @@ window.__ModuleLoader__.load({
|
|
|
3041
3987
|
background: "transparent",
|
|
3042
3988
|
border: "none",
|
|
3043
3989
|
padding: 0,
|
|
3044
|
-
cursor: "pointer"
|
|
3990
|
+
cursor: "pointer",
|
|
3991
|
+
marginTop: showGroupGap ? GROUP_GAP_EXTRA : 0
|
|
3045
3992
|
},
|
|
3046
3993
|
onMouseEnter: (e) => {
|
|
3047
3994
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
@@ -3052,6 +3999,8 @@ window.__ModuleLoader__.load({
|
|
|
3052
3999
|
},
|
|
3053
4000
|
onClick: () => jump(mark.key),
|
|
3054
4001
|
"data-rail-dot": true,
|
|
4002
|
+
"data-turn-gap": showGroupGap ? "true" : void 0,
|
|
4003
|
+
"data-turn": showGroupGap && mark.turn !== void 0 ? mark.turn : void 0,
|
|
3055
4004
|
"data-collapsed-summary": summaryCount !== void 0 ? "true" : void 0,
|
|
3056
4005
|
"data-collapsed-count": summaryCount,
|
|
3057
4006
|
tabIndex: focusIndex === i ? 0 : -1,
|
|
@@ -3063,10 +4012,10 @@ window.__ModuleLoader__.load({
|
|
|
3063
4012
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3064
4013
|
style: {
|
|
3065
4014
|
position: "relative",
|
|
3066
|
-
width:
|
|
3067
|
-
height:
|
|
4015
|
+
width: size,
|
|
4016
|
+
height: size,
|
|
3068
4017
|
borderRadius: "50%",
|
|
3069
|
-
background: dotColor(item.displayIndex, marks.length),
|
|
4018
|
+
background: dotColor(item.displayIndex, marks.length, accent),
|
|
3070
4019
|
boxShadow,
|
|
3071
4020
|
transition: "transform 120ms ease, opacity 120ms ease",
|
|
3072
4021
|
transform: `scale(${isHovered ? 1.35 : dotState === "active" || dotState === "current" ? 1.25 : 1})`,
|
|
@@ -3079,18 +4028,18 @@ window.__ModuleLoader__.load({
|
|
|
3079
4028
|
position: "absolute",
|
|
3080
4029
|
inset: -3,
|
|
3081
4030
|
borderRadius: "50%",
|
|
3082
|
-
|
|
4031
|
+
boxShadow: ringStyle.shadow,
|
|
3083
4032
|
color: ringStyle.color,
|
|
3084
4033
|
pointerEvents: "none",
|
|
3085
|
-
animation: ringStyle.pulse ? "milestone-badge-pulse
|
|
4034
|
+
animation: ringStyle.pulse ? "milestone-badge-pulse 2s ease-in-out infinite" : void 0
|
|
3086
4035
|
}
|
|
3087
4036
|
})
|
|
3088
4037
|
})
|
|
3089
|
-
})
|
|
4038
|
+
}) }, mark.key);
|
|
3090
4039
|
})
|
|
3091
4040
|
}),
|
|
3092
4041
|
hover !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MilestoneRailTooltip, {
|
|
3093
|
-
panelRight:
|
|
4042
|
+
panelRight: panelRightFor(TOOLTIP_ANCHOR_WIDTH),
|
|
3094
4043
|
hover,
|
|
3095
4044
|
bookmarked: isBookmarked(bookmarkedKeys, hover.mark.key),
|
|
3096
4045
|
onToggleBookmark: () => onToggleBookmark(hover.mark.key),
|
|
@@ -3109,8 +4058,13 @@ window.__ModuleLoader__.load({
|
|
|
3109
4058
|
style: {
|
|
3110
4059
|
position: "absolute",
|
|
3111
4060
|
bottom: 6,
|
|
3112
|
-
|
|
3113
|
-
|
|
4061
|
+
...side === "left" ? {
|
|
4062
|
+
left: "100%",
|
|
4063
|
+
marginLeft: 8
|
|
4064
|
+
} : {
|
|
4065
|
+
right: "100%",
|
|
4066
|
+
marginRight: 8
|
|
4067
|
+
},
|
|
3114
4068
|
whiteSpace: "nowrap",
|
|
3115
4069
|
fontSize: 12,
|
|
3116
4070
|
lineHeight: 1,
|
|
@@ -3233,213 +4187,6 @@ window.__ModuleLoader__.load({
|
|
|
3233
4187
|
});
|
|
3234
4188
|
}
|
|
3235
4189
|
//#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
4190
|
//#region src/client/index.ts
|
|
3444
4191
|
/** Required services (cordis fiber inject). */
|
|
3445
4192
|
const inject = [
|