dsh-mega-chat-nav 0.1.1

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.
Files changed (192) hide show
  1. package/LICENSE +201 -0
  2. package/README.en.md +149 -0
  3. package/README.md +149 -0
  4. package/cordis.patch.yml +4 -0
  5. package/lib/client/NavOverlay.js +35 -0
  6. package/lib/client/NavRail.js +12 -0
  7. package/lib/client/QuestionNavSettingsTab.js +26 -0
  8. package/lib/client/QuestionNavStrip.js +555 -0
  9. package/lib/client/components/ConfigPanel.js +14 -0
  10. package/lib/client/components/DotRail.js +62 -0
  11. package/lib/client/components/NavOverlay.js +121 -0
  12. package/lib/client/components/NavRail.js +97 -0
  13. package/lib/client/components/RailDispatch.js +22 -0
  14. package/lib/client/components/RailHost.js +79 -0
  15. package/lib/client/components/SearchBox.js +66 -0
  16. package/lib/client/components/SettingsPage.js +46 -0
  17. package/lib/client/components/StandaloneSettings.js +16 -0
  18. package/lib/client/components/codex/CodexRail.js +139 -0
  19. package/lib/client/components/deepseek/DeepseekRail.js +264 -0
  20. package/lib/client/components/minimal/MinimalRail.js +135 -0
  21. package/lib/client/components/minimal/cards.js +32 -0
  22. package/lib/client/components/minimal/dot.js +11 -0
  23. package/lib/client/components/minimal/hint.js +30 -0
  24. package/lib/client/components/minimal/paging.js +5 -0
  25. package/lib/client/components/minimal/quick-settings.js +28 -0
  26. package/lib/client/components/minimal/search.js +67 -0
  27. package/lib/client/components/minimal/useRail.js +716 -0
  28. package/lib/client/components/rail/cards.js +32 -0
  29. package/lib/client/components/rail/hint.js +30 -0
  30. package/lib/client/components/rail/paging.js +5 -0
  31. package/lib/client/components/rail/quick-settings.js +28 -0
  32. package/lib/client/components/rail/search.js +76 -0
  33. package/lib/client/components/rail/useRail.js +845 -0
  34. package/lib/client/components/settings/SettingsPage.js +46 -0
  35. package/lib/client/components/settings/SettingsTab.js +17 -0
  36. package/lib/client/components/settings/StandaloneSettings.js +16 -0
  37. package/lib/client/components/shared/Cascade.js +26 -0
  38. package/lib/client/components/shared/ConfigPanel.js +27 -0
  39. package/lib/client/components/shared/Dot.js +8 -0
  40. package/lib/client/components/shared/Hint.js +5 -0
  41. package/lib/client/components/shared/PagingButton.js +5 -0
  42. package/lib/client/components/shared/RailShell.js +78 -0
  43. package/lib/client/components/shared/SearchBox.js +63 -0
  44. package/lib/client/components/shared/hooks.js +427 -0
  45. package/lib/client/components/shared/types.js +1 -0
  46. package/lib/client/config.js +1 -0
  47. package/lib/client/engines/jump.js +34 -0
  48. package/lib/client/engines/useAtBottom.js +25 -0
  49. package/lib/client/engines/useJump.js +40 -0
  50. package/lib/client/engines/useScrollSpy.js +88 -0
  51. package/lib/client/hooks.js +11 -0
  52. package/lib/client/index.js +312 -0
  53. package/lib/client/jump.js +255 -0
  54. package/lib/client/locales.js +142 -0
  55. package/lib/client/pure.js +61 -0
  56. package/lib/client/settings.js +369 -0
  57. package/lib/client/virtual/VirtualList.js +21 -0
  58. package/lib/client.js +3213 -0
  59. package/lib/compat.js +145 -0
  60. package/lib/config.js +37 -0
  61. package/lib/core/active-dot.js +17 -0
  62. package/lib/core/align.js +9 -0
  63. package/lib/core/config.js +37 -0
  64. package/lib/core/focus.js +38 -0
  65. package/lib/core/jump.js +98 -0
  66. package/lib/core/logic/active-dot.js +16 -0
  67. package/lib/core/logic/focus.js +34 -0
  68. package/lib/core/logic/jump.js +188 -0
  69. package/lib/core/logic/nodes.js +59 -0
  70. package/lib/core/model/nav-item.js +1 -0
  71. package/lib/core/model/turns.js +73 -0
  72. package/lib/core/nav-item.js +1 -0
  73. package/lib/core/nodes.js +57 -0
  74. package/lib/core/page-size.js +7 -0
  75. package/lib/core/question-entry.js +1 -0
  76. package/lib/core/time.js +23 -0
  77. package/lib/core/turn-dots.js +85 -0
  78. package/lib/core/utils/measure.js +24 -0
  79. package/lib/core/utils/time.js +26 -0
  80. package/lib/index.js +64 -0
  81. package/lib/nav-item.js +1 -0
  82. package/lib/nodes.js +59 -0
  83. package/lib/projection.js +126 -0
  84. package/lib/schema.js +26 -0
  85. package/lib/search.js +285 -0
  86. package/lib/settings.js +89 -0
  87. package/lib/types/client/NavOverlay.d.ts +18 -0
  88. package/lib/types/client/NavRail.d.ts +7 -0
  89. package/lib/types/client/QuestionNavSettingsTab.d.ts +16 -0
  90. package/lib/types/client/QuestionNavStrip.d.ts +48 -0
  91. package/lib/types/client/components/ConfigPanel.d.ts +14 -0
  92. package/lib/types/client/components/DotRail.d.ts +15 -0
  93. package/lib/types/client/components/NavOverlay.d.ts +30 -0
  94. package/lib/types/client/components/NavRail.d.ts +16 -0
  95. package/lib/types/client/components/RailDispatch.d.ts +8 -0
  96. package/lib/types/client/components/RailHost.d.ts +27 -0
  97. package/lib/types/client/components/SearchBox.d.ts +9 -0
  98. package/lib/types/client/components/SettingsPage.d.ts +9 -0
  99. package/lib/types/client/components/StandaloneSettings.d.ts +13 -0
  100. package/lib/types/client/components/codex/CodexRail.d.ts +3 -0
  101. package/lib/types/client/components/deepseek/DeepseekRail.d.ts +3 -0
  102. package/lib/types/client/components/minimal/MinimalRail.d.ts +8 -0
  103. package/lib/types/client/components/minimal/cards.d.ts +19 -0
  104. package/lib/types/client/components/minimal/dot.d.ts +14 -0
  105. package/lib/types/client/components/minimal/hint.d.ts +11 -0
  106. package/lib/types/client/components/minimal/paging.d.ts +10 -0
  107. package/lib/types/client/components/minimal/quick-settings.d.ts +8 -0
  108. package/lib/types/client/components/minimal/search.d.ts +27 -0
  109. package/lib/types/client/components/minimal/useRail.d.ts +90 -0
  110. package/lib/types/client/components/rail/cards.d.ts +19 -0
  111. package/lib/types/client/components/rail/hint.d.ts +11 -0
  112. package/lib/types/client/components/rail/paging.d.ts +10 -0
  113. package/lib/types/client/components/rail/quick-settings.d.ts +8 -0
  114. package/lib/types/client/components/rail/search.d.ts +34 -0
  115. package/lib/types/client/components/rail/useRail.d.ts +132 -0
  116. package/lib/types/client/components/settings/SettingsPage.d.ts +9 -0
  117. package/lib/types/client/components/settings/SettingsTab.d.ts +6 -0
  118. package/lib/types/client/components/settings/StandaloneSettings.d.ts +13 -0
  119. package/lib/types/client/components/shared/Cascade.d.ts +14 -0
  120. package/lib/types/client/components/shared/ConfigPanel.d.ts +8 -0
  121. package/lib/types/client/components/shared/Dot.d.ts +17 -0
  122. package/lib/types/client/components/shared/Hint.d.ts +6 -0
  123. package/lib/types/client/components/shared/PagingButton.d.ts +10 -0
  124. package/lib/types/client/components/shared/RailShell.d.ts +43 -0
  125. package/lib/types/client/components/shared/SearchBox.d.ts +18 -0
  126. package/lib/types/client/components/shared/hooks.d.ts +64 -0
  127. package/lib/types/client/components/shared/types.d.ts +101 -0
  128. package/lib/types/client/config.d.ts +9 -0
  129. package/lib/types/client/engines/jump.d.ts +17 -0
  130. package/lib/types/client/engines/useAtBottom.d.ts +1 -0
  131. package/lib/types/client/engines/useJump.d.ts +16 -0
  132. package/lib/types/client/engines/useScrollSpy.d.ts +12 -0
  133. package/lib/types/client/hooks.d.ts +7 -0
  134. package/lib/types/client/index.d.ts +31 -0
  135. package/lib/types/client/jump.d.ts +47 -0
  136. package/lib/types/client/locales.d.ts +142 -0
  137. package/lib/types/client/pure.d.ts +21 -0
  138. package/lib/types/client/settings.d.ts +173 -0
  139. package/lib/types/client/virtual/VirtualList.d.ts +15 -0
  140. package/lib/types/compat.d.ts +44 -0
  141. package/lib/types/config.d.ts +44 -0
  142. package/lib/types/core/active-dot.d.ts +15 -0
  143. package/lib/types/core/align.d.ts +10 -0
  144. package/lib/types/core/config.d.ts +44 -0
  145. package/lib/types/core/focus.d.ts +24 -0
  146. package/lib/types/core/jump.d.ts +39 -0
  147. package/lib/types/core/logic/active-dot.d.ts +15 -0
  148. package/lib/types/core/logic/focus.d.ts +22 -0
  149. package/lib/types/core/logic/jump.d.ts +39 -0
  150. package/lib/types/core/logic/nodes.d.ts +48 -0
  151. package/lib/types/core/model/nav-item.d.ts +12 -0
  152. package/lib/types/core/model/turns.d.ts +30 -0
  153. package/lib/types/core/nav-item.d.ts +12 -0
  154. package/lib/types/core/nodes.d.ts +43 -0
  155. package/lib/types/core/page-size.d.ts +8 -0
  156. package/lib/types/core/question-entry.d.ts +12 -0
  157. package/lib/types/core/time.d.ts +4 -0
  158. package/lib/types/core/turn-dots.d.ts +29 -0
  159. package/lib/types/core/utils/measure.d.ts +12 -0
  160. package/lib/types/core/utils/time.d.ts +4 -0
  161. package/lib/types/index.d.ts +4 -0
  162. package/lib/types/nav-item.d.ts +12 -0
  163. package/lib/types/nodes.d.ts +48 -0
  164. package/lib/types/projection.d.ts +71 -0
  165. package/lib/types/schema.d.ts +37 -0
  166. package/lib/types/search.d.ts +42 -0
  167. package/lib/types/settings.d.ts +99 -0
  168. package/package.json +71 -0
  169. package/src/client/components/RailDispatch.tsx +27 -0
  170. package/src/client/components/codex/CodexRail.tsx +348 -0
  171. package/src/client/components/deepseek/DeepseekRail.tsx +522 -0
  172. package/src/client/components/minimal/MinimalRail.tsx +324 -0
  173. package/src/client/components/minimal/dot.tsx +32 -0
  174. package/src/client/components/rail/cards.tsx +102 -0
  175. package/src/client/components/rail/hint.tsx +50 -0
  176. package/src/client/components/rail/paging.tsx +26 -0
  177. package/src/client/components/rail/quick-settings.tsx +130 -0
  178. package/src/client/components/rail/search.tsx +147 -0
  179. package/src/client/components/rail/useRail.ts +906 -0
  180. package/src/client/components/settings/SettingsPage.tsx +198 -0
  181. package/src/client/components/settings/StandaloneSettings.tsx +28 -0
  182. package/src/client/components/shared/types.ts +99 -0
  183. package/src/client/index.ts +388 -0
  184. package/src/client/jump.ts +293 -0
  185. package/src/client/locales.ts +143 -0
  186. package/src/client/settings.ts +482 -0
  187. package/src/client/styles.css +1580 -0
  188. package/src/compat.ts +161 -0
  189. package/src/index.ts +66 -0
  190. package/src/projection.ts +174 -0
  191. package/src/search.ts +291 -0
  192. package/src/settings.ts +169 -0
@@ -0,0 +1,142 @@
1
+ /**
2
+ * mega-chat-nav 词条字典(zh/en)。注册于 `mega-chat-nav` 命名空间;
3
+ * key 经绑定翻译函数消费。
4
+ */
5
+ export const zh = {
6
+ 'strip.empty': '本会话还没有提问',
7
+ 'strip.up': '向上翻出更多提问圆点',
8
+ 'strip.down': '向下翻出更多提问圆点',
9
+ 'jump.inactive': '聊天视图未激活',
10
+ 'jump.hidden': '目标无独立气泡,已定位到邻近内容',
11
+ 'jump.notfound': '目标未加载或不存在(可能已压缩)',
12
+ 'jump.timeout': '加载历史超时,可重试',
13
+ 'jump.loading': '正在加载较早记录以定位(第 {n} 页)',
14
+ 'search.historic': '另有 {n} 条来自更早历史',
15
+ 'search.turnTitle': '第 {turn} 轮',
16
+ 'search.tag.user': '用户',
17
+ 'search.tag.assistant': '助手',
18
+ 'search.tag.tool': '工具',
19
+ 'settings.tab': '提问导航',
20
+ 'label': 'mega 导航',
21
+ 'config.align': '对齐方式',
22
+ 'config.railOffset': '导航栏偏移',
23
+ 'config.railOffset.desc': '导航条与对话列边缘的距离(0-32px,随左右对齐自适应)。',
24
+ 'settings.align.desc': '选择圆点导航条锚定在对话栏的哪一侧。',
25
+ 'settings.align.left': '左侧',
26
+ 'settings.align.right': '右侧',
27
+ 'settings.hosted': '配置已收纳至 mega 设置中心',
28
+ 'config.bandHeight': '节点栏高度',
29
+ 'config.bandHeight.desc': '节点栏的最大高度。',
30
+ 'config.bandHeight.compact': '紧凑',
31
+ 'config.bandHeight.standard': '标准',
32
+ 'config.bandHeight.tall': '高',
33
+ 'config.title': '导航条设置',
34
+ 'config.settings': '设置',
35
+ 'config.close': '关闭',
36
+ 'config.scrollBehavior': '跳转动画',
37
+ 'config.scrollBehavior.desc': '点击跳转时消息滚入的动画方式。',
38
+ 'config.scrollBehavior.smooth': '平滑',
39
+ 'config.scrollBehavior.instant': '瞬时',
40
+ 'config.showCount': '轮次总数',
41
+ 'config.showPaging': '翻页标记',
42
+ 'config.showQuickSettings': '设置按钮',
43
+ 'config.showFavorites': '收藏按钮',
44
+ 'config.searchScopes': '搜索内容',
45
+ 'config.searchScopes.desc': '搜索哪些消息类型(多选;用户必选)。',
46
+ 'config.searchScopes.user': '用户',
47
+ 'config.searchScopes.assistant': '助手',
48
+ 'config.searchScopes.tool': '工具',
49
+ 'config.style': '风格',
50
+ 'config.style.minimal': '简约',
51
+ 'config.style.codex': 'Codex',
52
+ 'config.style.deepseek': 'Deepseek Chat',
53
+ 'config.cardCount': '悬停卡片数',
54
+ 'config.cardCount.desc': '悬停节点时弹出的卡片数量(1/3/5 张)。',
55
+ 'config.cardItems': '悬浮卡功能',
56
+ 'config.cardItems.desc': '勾选悬浮卡显示的内容块(多选)。',
57
+ 'config.cardItems.turn': '轮次',
58
+ 'config.cardItems.duration': '用时',
59
+ 'config.cardItems.favorite': '收藏',
60
+ 'config.markTone': '刻度色调',
61
+ 'config.markTone.desc': 'codex 刻度条的明暗档:柔和低对比 / 深邃高对比(默认)。',
62
+ 'config.markTone.soft': '柔和',
63
+ 'config.markTone.deep': '深邃',
64
+ 'fav.filter': '只看收藏',
65
+ 'strip.noFavorites': '暂无收藏',
66
+ 'config.showSearch': '搜索按钮',
67
+ 'config.showMode.show': '显示',
68
+ 'config.showMode.peek': '浮现',
69
+ 'config.showMode.hide': '隐藏',
70
+ 'search.placeholder': '搜索消息…',
71
+ 'search.empty': '无匹配结果',
72
+ 'search.loading': '正在搜索…',
73
+ };
74
+ export const en = {
75
+ 'strip.empty': 'No questions in this session yet',
76
+ 'strip.up': 'Reveal more question dots above',
77
+ 'strip.down': 'Reveal more question dots below',
78
+ 'jump.inactive': 'Chat view is not active',
79
+ 'jump.hidden': 'No dedicated bubble; landed on nearby content',
80
+ 'jump.notfound': 'Target not loaded or missing (maybe compacted)',
81
+ 'jump.timeout': 'Timed out loading history; retry',
82
+ 'jump.loading': 'Loading earlier records to locate (page {n})',
83
+ 'search.historic': '{n} more from earlier history',
84
+ 'search.turnTitle': 'Turn {turn}',
85
+ 'search.tag.user': 'User',
86
+ 'search.tag.assistant': 'Assistant',
87
+ 'search.tag.tool': 'Tool',
88
+ 'settings.tab': 'Question Nav',
89
+ 'label': 'Mega Nav',
90
+ 'config.align': 'Alignment',
91
+ 'config.railOffset': 'Rail offset',
92
+ 'config.railOffset.desc': 'Distance between the rail and the conversation edge (0-32px, adapts to alignment).',
93
+ 'settings.align.desc': 'Choose which edge of the conversation column the dot rail anchors to.',
94
+ 'settings.align.left': 'Left',
95
+ 'settings.align.right': 'Right',
96
+ 'settings.hosted': 'Config is hosted in the mega settings center',
97
+ 'config.bandHeight': 'Band height',
98
+ 'config.bandHeight.desc': 'Max height of the dot band.',
99
+ 'config.bandHeight.compact': 'Compact',
100
+ 'config.bandHeight.standard': 'Standard',
101
+ 'config.bandHeight.tall': 'Tall',
102
+ 'config.title': 'Nav settings',
103
+ 'config.settings': 'Settings',
104
+ 'config.close': 'Close',
105
+ 'config.scrollBehavior': 'Jump animation',
106
+ 'config.scrollBehavior.desc': 'How the chat scrolls when jumping to a message.',
107
+ 'config.scrollBehavior.smooth': 'Smooth',
108
+ 'config.scrollBehavior.instant': 'Instant',
109
+ 'config.showCount': 'Turn count',
110
+ 'config.showPaging': 'Paging markers',
111
+ 'config.showQuickSettings': 'Settings button',
112
+ 'config.showFavorites': 'Favorite button',
113
+ 'config.searchScopes': 'Search content',
114
+ 'config.searchScopes.desc': 'Message types included in search (multi-select; user is required).',
115
+ 'config.searchScopes.user': 'User',
116
+ 'config.searchScopes.assistant': 'Assistant',
117
+ 'config.searchScopes.tool': 'Tool',
118
+ 'config.style': 'Style',
119
+ 'config.style.minimal': 'Minimal',
120
+ 'config.style.codex': 'Codex',
121
+ 'config.style.deepseek': 'Deepseek Chat',
122
+ 'config.cardCount': 'Card count',
123
+ 'config.cardCount.desc': 'Cascade cards shown on hover (1/3/5).',
124
+ 'config.cardItems': 'Card contents',
125
+ 'config.cardItems.desc': 'Content blocks shown on hover cards (multi-select).',
126
+ 'config.cardItems.turn': 'Turn',
127
+ 'config.cardItems.duration': 'Duration',
128
+ 'config.cardItems.favorite': 'Favorite',
129
+ 'config.markTone': 'Mark tone',
130
+ 'config.markTone.desc': 'Codex tick contrast: soft / deep (default).',
131
+ 'config.markTone.soft': 'Soft',
132
+ 'config.markTone.deep': 'Deep',
133
+ 'fav.filter': 'Favorites only',
134
+ 'strip.noFavorites': 'No favorites yet',
135
+ 'config.showSearch': 'Search button',
136
+ 'config.showMode.show': 'Show',
137
+ 'config.showMode.peek': 'Peek',
138
+ 'config.showMode.hide': 'Hide',
139
+ 'search.placeholder': 'Search messages…',
140
+ 'search.empty': 'No matches',
141
+ 'search.loading': 'Searching…',
142
+ };
@@ -0,0 +1,61 @@
1
+ /** TabGate:chat 视图可见(R-10)——滚动容器存在且几何 >0 */
2
+ export function isChatViewVisible(doc) {
3
+ const el = doc.querySelector('[data-conversation-scroll]');
4
+ if (!el)
5
+ return false;
6
+ const r = el.getBoundingClientRect();
7
+ return r.width > 0 && r.height > 0;
8
+ }
9
+ /** TabGate:Trajectory(轨迹)视图在场时 nav 停用 */
10
+ export function hasTrajectoryView(doc) {
11
+ return !!doc.querySelector('[data-trajectory-scroll]');
12
+ }
13
+ /** 锚点规则 → 阅读线 Y(视口坐标系) */
14
+ export function readingLineY(viewport, rule) {
15
+ switch (rule) {
16
+ case 'top': return viewport.top;
17
+ case 'center': return viewport.top + viewport.height * 0.5;
18
+ case 'line40':
19
+ default: return viewport.top + viewport.height * 0.4;
20
+ }
21
+ }
22
+ /** 距阅读线最近条目索引(40% 线主流判据;无条目返回 -1) */
23
+ export function nearestIndex(total, line, yOf) {
24
+ if (total <= 0)
25
+ return -1;
26
+ let best = 0;
27
+ let bestDist = Infinity;
28
+ for (let i = 0; i < total; i++) {
29
+ const d = Math.abs(yOf(i) - line);
30
+ if (d < bestDist) {
31
+ bestDist = d;
32
+ best = i;
33
+ }
34
+ }
35
+ return best;
36
+ }
37
+ /** 虚拟滚动窗口(可见 + 前后缓冲;hover/current 由调用方钉住) */
38
+ export function virtualWindow(total, scrollTop, viewportH, itemH, buffer = 24) {
39
+ if (total <= 0 || itemH <= 0)
40
+ return { start: 0, end: 0 };
41
+ const start = Math.max(0, Math.floor(scrollTop / itemH) - buffer);
42
+ const end = Math.min(total, Math.ceil((scrollTop + viewportH) / itemH) + buffer);
43
+ return { start, end };
44
+ }
45
+ /** 相对时间(秒/分钟/小时/天) */
46
+ export function formatTime(ts, now = Date.now()) {
47
+ const d = Math.max(0, now - ts);
48
+ if (d < 60_000)
49
+ return `${Math.floor(d / 1000)}s`;
50
+ if (d < 3_600_000)
51
+ return `${Math.floor(d / 60_000)}m`;
52
+ if (d < 86_400_000)
53
+ return `${Math.floor(d / 3_600_000)}h`;
54
+ return `${Math.floor(d / 86_400_000)}d`;
55
+ }
56
+ /** 截断到 n 字符并加省略号 */
57
+ export function truncate(s, n) {
58
+ if (s.length <= n)
59
+ return s;
60
+ return s.slice(0, Math.max(0, n - 1)) + '…';
61
+ }
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Browser mirror of the `mega-chat-nav` settings namespace: reads the rail
3
+ * settings from the settings scope (`ctx.settingsScope.bind`) and routes the
4
+ * user's choices back through the host bridge (POST /mega-chat-nav/settings).
5
+ *
6
+ * 配置选项为本插件自有定义:此处为 client 半持有的同值副本(与 host 半
7
+ * src/settings.ts 一致;client bundle 不得依赖宿主包)。
8
+ *
9
+ * The settings surface is optional and may apply after this plugin, so the
10
+ * controller starts unbound and degrades to the defaults until {@link attach}
11
+ * binds the scope.
12
+ *
13
+ * @module dsh-mega-chat-nav/client/settings
14
+ */
15
+ /** 风格选项:简约 / codex(时间线刻度)/ deepseek(用户消息面板,常态短横折叠) */
16
+ export const STYLE_OPTIONS = ['minimal', 'codex', 'deepseek'];
17
+ /** 停靠侧选项 */
18
+ export const ALIGN_OPTIONS = ['left', 'right'];
19
+ /** 节点栏条带高度档位 */
20
+ export const BAND_OPTIONS = ['compact', 'standard', 'tall'];
21
+ /** 跳转动画方式 */
22
+ export const SCROLL_OPTIONS = ['smooth', 'instant'];
23
+ /** 显示策略:常显 / 悬停浮现 / 隐藏 */
24
+ export const SHOW_OPTIONS = ['show', 'peek', 'hide'];
25
+ /** 翻页标记显示策略(按风格独立默认):minimal 常显 / codex 隐藏 / deepseek 浮现 */
26
+ export const DEFAULT_PAGING = {
27
+ minimal: 'show',
28
+ codex: 'hide',
29
+ deepseek: 'peek',
30
+ };
31
+ /** 搜索内容范围(多选;用户必选) */
32
+ export const SEARCH_SCOPE_OPTIONS = ['user', 'assistant', 'tool'];
33
+ export const DEFAULT_SEARCH_SCOPES = ['user', 'assistant', 'tool'];
34
+ /** 悬停卡片数量 */
35
+ export const CARD_OPTIONS = [1, 3, 5];
36
+ /** 悬浮卡默认数量(按风格独立):minimal 3 张 / codex 1 张 / deepseek 1 张(无悬浮卡能力,占位) */
37
+ export const DEFAULT_CARD_COUNT = {
38
+ minimal: 3,
39
+ codex: 1,
40
+ deepseek: 1,
41
+ };
42
+ /** 悬浮卡内容块(多选) */
43
+ export const CARD_ITEM_OPTIONS = ['turn', 'duration', 'favorite'];
44
+ export const DEFAULT_CARD_ITEMS = ['turn', 'duration', 'favorite'];
45
+ /** 刻度色调:柔和(低对比)/ 深邃(高对比、清晰) */
46
+ export const MARK_TONE_OPTIONS = ['soft', 'deep'];
47
+ export const DEFAULT_MARK_TONE = 'deep';
48
+ export const DEFAULT_STYLE = 'minimal';
49
+ export const DEFAULT_ALIGN = 'left';
50
+ export const DEFAULT_BAND = 'standard';
51
+ export const DEFAULT_SCROLL = 'smooth';
52
+ export const DEFAULT_SHOW = 'show';
53
+ /** 条带高度档位 → 像素(minimal/codex 共用基础几何;deepseek 行高 30px 自成一档)
54
+ * - minimal / codex:compact 140 / standard 210 / tall 280
55
+ * - deepseek:compact 160 / standard 220 / tall 280 */
56
+ export const BAND_HEIGHT_PX = {
57
+ minimal: { compact: 144, standard: 204, tall: 274 },
58
+ codex: { compact: 140, standard: 210, tall: 280 },
59
+ deepseek: { compact: 160, standard: 220, tall: 280 },
60
+ };
61
+ /** 风格 → 能力表 */
62
+ export const STYLE_CAPABILITIES = {
63
+ minimal: { hoverCards: true, paging: true },
64
+ codex: { hoverCards: true, paging: true },
65
+ deepseek: { hoverCards: false, paging: false },
66
+ };
67
+ /** 本插件设置命名空间 */
68
+ export const MGCN_NS = 'mega-chat-nav';
69
+ /** 导航栏偏移(px)范围与默认值 */
70
+ export const RAIL_OFFSET_MIN = 0;
71
+ export const RAIL_OFFSET_MAX = 32;
72
+ export const DEFAULT_RAIL_OFFSET = 8;
73
+ /** Narrow a raw section value to the anchor field. */
74
+ function isRailAlign(value) {
75
+ return ALIGN_OPTIONS.includes(value);
76
+ }
77
+ /** Narrow a raw section value to the style field. */
78
+ function isNavStyle(value) {
79
+ return STYLE_OPTIONS.includes(value);
80
+ }
81
+ /** 兼容旧 boolean 配置:true → show,false → hide;新值走枚举校验 */
82
+ function coerceShowMode(value) {
83
+ if (typeof value === 'boolean')
84
+ return value ? 'show' : 'hide';
85
+ return isShowMode(value) ? value : DEFAULT_SHOW;
86
+ }
87
+ /** 翻页标记显示模式转换(默认按风格:minimal show / codex hide) */
88
+ function coercePagingMode(value, fallback) {
89
+ if (typeof value === 'boolean')
90
+ return value ? 'show' : 'hide';
91
+ return isShowMode(value) ? value : fallback;
92
+ }
93
+ function isShowMode(value) {
94
+ return SHOW_OPTIONS.includes(value);
95
+ }
96
+ function isCardCount(value) {
97
+ return CARD_OPTIONS.includes(value);
98
+ }
99
+ function isBandHeight(value) {
100
+ return BAND_OPTIONS.includes(value);
101
+ }
102
+ function isSearchScopes(value) {
103
+ return Array.isArray(value)
104
+ && value.every((s) => SEARCH_SCOPE_OPTIONS.includes(s));
105
+ }
106
+ function isCardItems(value) {
107
+ return Array.isArray(value)
108
+ && value.every((item) => CARD_ITEM_OPTIONS.includes(item));
109
+ }
110
+ function isMarkTone(value) {
111
+ return MARK_TONE_OPTIONS.includes(value);
112
+ }
113
+ function isScrollMode(value) {
114
+ return SCROLL_OPTIONS.includes(value);
115
+ }
116
+ /** Reactive handle over the plugin's durable settings section. */
117
+ export class NavSettingsController {
118
+ scope;
119
+ listeners = new Set();
120
+ unsubscribe = () => { };
121
+ state = {
122
+ style: DEFAULT_STYLE,
123
+ align: DEFAULT_ALIGN,
124
+ bandHeight: DEFAULT_BAND,
125
+ scrollBehavior: DEFAULT_SCROLL,
126
+ showCount: DEFAULT_SHOW,
127
+ showSearch: DEFAULT_SHOW,
128
+ showQuickSettings: DEFAULT_SHOW,
129
+ showFavorites: DEFAULT_SHOW,
130
+ showPaging: DEFAULT_PAGING.minimal,
131
+ searchScopes: [...DEFAULT_SEARCH_SCOPES],
132
+ railOffset: DEFAULT_RAIL_OFFSET,
133
+ cardCount: DEFAULT_CARD_COUNT[DEFAULT_STYLE],
134
+ cardItems: [...DEFAULT_CARD_ITEMS],
135
+ markTone: DEFAULT_MARK_TONE,
136
+ overridden: false,
137
+ };
138
+ /**
139
+ * Bind the namespace scope once the settings surface is present.
140
+ * A no-op after the first bind.
141
+ */
142
+ attach(binder) {
143
+ if (this.scope !== undefined)
144
+ return;
145
+ this.scope = binder.bind({ namespace: MGCN_NS });
146
+ this.state = this.derive(this.scope.getSnapshot());
147
+ this.unsubscribe = this.scope.subscribe(() => {
148
+ if (this.scope === undefined)
149
+ return;
150
+ const next = this.derive(this.scope.getSnapshot());
151
+ if (next.style === this.state.style
152
+ && next.align === this.state.align && next.bandHeight === this.state.bandHeight
153
+ && next.cardCount === this.state.cardCount
154
+ && next.scrollBehavior === this.state.scrollBehavior
155
+ && next.showCount === this.state.showCount
156
+ && next.showSearch === this.state.showSearch
157
+ && next.showQuickSettings === this.state.showQuickSettings
158
+ && next.overridden === this.state.overridden)
159
+ return;
160
+ this.state = next;
161
+ for (const listener of this.listeners)
162
+ listener();
163
+ });
164
+ }
165
+ /** 当前生效风格的 styles.<style> 段(快照内读取,供逐风格独立取值) */
166
+ styleSection(snapshot, style) {
167
+ return snapshot.value?.styles?.[style];
168
+ }
169
+ derive(snapshot) {
170
+ const user = snapshot.user;
171
+ const general = snapshot.value?.general;
172
+ const ready = snapshot.status === 'ready';
173
+ const style = ready && isNavStyle(general?.style) ? general.style : DEFAULT_STYLE;
174
+ const active = this.styleSection(snapshot, style);
175
+ return {
176
+ style,
177
+ align: ready && isRailAlign(general?.align) ? general.align : DEFAULT_ALIGN,
178
+ bandHeight: ready && isBandHeight(active?.bandHeight) ? active.bandHeight : DEFAULT_BAND,
179
+ cardCount: ready && isCardCount(active?.cardCount) ? active.cardCount : DEFAULT_CARD_COUNT[style],
180
+ cardItems: ready && isCardItems(active?.cardItems) ? active.cardItems : [...DEFAULT_CARD_ITEMS],
181
+ markTone: ready && isMarkTone(active?.markTone) ? active.markTone : DEFAULT_MARK_TONE,
182
+ scrollBehavior: ready && isScrollMode(general?.scrollBehavior) ? general.scrollBehavior : DEFAULT_SCROLL,
183
+ showCount: ready ? coerceShowMode(general?.showCount) : DEFAULT_SHOW,
184
+ showSearch: ready ? coerceShowMode(general?.showSearch) : DEFAULT_SHOW,
185
+ showQuickSettings: ready ? coerceShowMode(general?.showQuickSettings) : DEFAULT_SHOW,
186
+ showFavorites: ready ? coerceShowMode(general?.showFavorites) : DEFAULT_SHOW,
187
+ showPaging: ready ? coercePagingMode(active?.showPaging, DEFAULT_PAGING[style]) : DEFAULT_PAGING[style],
188
+ searchScopes: ready && isSearchScopes(general?.searchScopes) ? general.searchScopes : [...DEFAULT_SEARCH_SCOPES],
189
+ railOffset: ready && typeof general?.railOffset === 'number' && general.railOffset >= 0 && general.railOffset <= 32
190
+ ? Math.round(general.railOffset)
191
+ : DEFAULT_RAIL_OFFSET,
192
+ overridden: user !== undefined && (user.general !== undefined || user.styles !== undefined),
193
+ };
194
+ }
195
+ /** Release the scope subscription. */
196
+ dispose() {
197
+ this.unsubscribe();
198
+ this.listeners.clear();
199
+ }
200
+ /** 乐观提交:更新状态并立即通知(UI 即时反映) */
201
+ commit(next) {
202
+ this.state = next;
203
+ for (const listener of this.listeners)
204
+ listener();
205
+ }
206
+ /** 当前 general 段(setter 整段写回用) */
207
+ get general() {
208
+ return {
209
+ style: this.state.style,
210
+ align: this.state.align,
211
+ scrollBehavior: this.state.scrollBehavior,
212
+ showCount: this.state.showCount,
213
+ showSearch: this.state.showSearch,
214
+ showQuickSettings: this.state.showQuickSettings,
215
+ showFavorites: this.state.showFavorites,
216
+ searchScopes: this.state.searchScopes,
217
+ railOffset: this.state.railOffset,
218
+ };
219
+ }
220
+ /** 当前风格段的完整值(setter 局部 patch 的基础) */
221
+ get styleValues() {
222
+ return {
223
+ bandHeight: this.state.bandHeight,
224
+ cardCount: this.state.cardCount,
225
+ cardItems: this.state.cardItems,
226
+ markTone: this.state.markTone,
227
+ showPaging: this.state.showPaging,
228
+ };
229
+ }
230
+ /** 按当前风格构造 write 的 styles 段(只含当前风格键,绝不染指其他风格段) */
231
+ stylePatch(extra) {
232
+ const section = { ...this.styleValues, ...extra };
233
+ const patch = {};
234
+ patch[this.state.style] = section;
235
+ return patch;
236
+ }
237
+ /** @returns the current state (stable reference until the next change). */
238
+ getSnapshot() {
239
+ return this.state;
240
+ }
241
+ /** Observe state replacements; returns the disposer. */
242
+ subscribe(listener) {
243
+ this.listeners.add(listener);
244
+ return () => { this.listeners.delete(listener); };
245
+ }
246
+ /**
247
+ * 写回宿主:scope.set 契约只接受 section 内**标量**字段,嵌套 section
248
+ * (general/styles 对象)无法经它持久化,所以走 host 桥
249
+ * (POST /mega-chat-nav/settings → settings.update 深层 merge + revision 围栏)。
250
+ */
251
+ write(patch) {
252
+ if (this.scope === undefined)
253
+ return;
254
+ const revision = this.scope.getSnapshot().revision;
255
+ void fetch('/mega-chat-nav/settings', {
256
+ method: 'POST',
257
+ headers: { 'Content-Type': 'application/json' },
258
+ body: JSON.stringify({ patch, revision: revision ?? undefined }),
259
+ })
260
+ .then((resp) => resp.json().catch(() => ({})))
261
+ .then((data) => {
262
+ if (!data.ok)
263
+ console.error('[mega-chat-nav] settings write rejected');
264
+ })
265
+ .catch((e) => console.error('[mega-chat-nav] settings write failed:', e));
266
+ }
267
+ /** Route the user's anchor-edge choice to the Host document. */
268
+ setAlign(align) {
269
+ if (this.scope === undefined)
270
+ return;
271
+ this.commit({ ...this.state, align });
272
+ this.write({ general: { ...this.general, align } });
273
+ }
274
+ /** Route the user's style choice to the Host document(切换后随即载入该风格自己的独立配置) */
275
+ setStyle(style) {
276
+ if (this.scope === undefined)
277
+ return;
278
+ const snapshot = this.scope.getSnapshot();
279
+ const next = this.derive({ ...snapshot, value: { ...snapshot.value, general: { ...snapshot.value?.general, style } } });
280
+ this.commit({ ...next, overridden: this.state.overridden });
281
+ this.write({ general: { ...this.general, style } });
282
+ }
283
+ /** Route the user's band-height choice to the Host document(写入当前风格的独立段) */
284
+ setBandHeight(band) {
285
+ if (this.scope === undefined)
286
+ return;
287
+ this.commit({ ...this.state, bandHeight: band });
288
+ this.write({ styles: this.stylePatch({ bandHeight: band }) });
289
+ }
290
+ /** Route the user's scroll-mode choice to the Host document. */
291
+ setScrollMode(mode) {
292
+ if (this.scope === undefined)
293
+ return;
294
+ this.commit({ ...this.state, scrollBehavior: mode });
295
+ this.write({ general: { ...this.general, scrollBehavior: mode } });
296
+ }
297
+ /** Route the user's count-badge display mode to the Host document. */
298
+ setShowCount(mode) {
299
+ if (this.scope === undefined)
300
+ return;
301
+ this.commit({ ...this.state, showCount: mode });
302
+ this.write({ general: { ...this.general, showCount: mode } });
303
+ }
304
+ /** Route the user's search-button display mode to the Host document. */
305
+ setShowSearch(mode) {
306
+ if (this.scope === undefined)
307
+ return;
308
+ this.commit({ ...this.state, showSearch: mode });
309
+ this.write({ general: { ...this.general, showSearch: mode } });
310
+ }
311
+ /** Route the user's quick-settings display mode to the Host document. */
312
+ setShowQuickSettings(mode) {
313
+ if (this.scope === undefined)
314
+ return;
315
+ this.commit({ ...this.state, showQuickSettings: mode });
316
+ this.write({ general: { ...this.general, showQuickSettings: mode } });
317
+ }
318
+ /** Route the user's favorite-button display mode to the Host document. */
319
+ setShowFavorites(mode) {
320
+ if (this.scope === undefined)
321
+ return;
322
+ this.commit({ ...this.state, showFavorites: mode });
323
+ this.write({ general: { ...this.general, showFavorites: mode } });
324
+ }
325
+ /** Route the user's paging-marker display mode to the Host document(写入当前风格独立段) */
326
+ setShowPaging(mode) {
327
+ if (this.scope === undefined)
328
+ return;
329
+ this.commit({ ...this.state, showPaging: mode });
330
+ this.write({ styles: this.stylePatch({ showPaging: mode }) });
331
+ }
332
+ /** Route the user's search-scope selection to the Host document(用户必选,强制保留且置于首位) */
333
+ setSearchScopes(scopes) {
334
+ if (this.scope === undefined)
335
+ return;
336
+ const normalized = ['user', ...scopes.filter((s) => s !== 'user')];
337
+ this.commit({ ...this.state, searchScopes: normalized });
338
+ this.write({ general: { ...this.general, searchScopes: normalized } });
339
+ }
340
+ /** Route the user's rail-offset choice to the Host document(0-32px 取整钳制) */
341
+ setRailOffset(offset) {
342
+ if (this.scope === undefined)
343
+ return;
344
+ const clamped = Math.max(RAIL_OFFSET_MIN, Math.min(RAIL_OFFSET_MAX, Math.round(offset)));
345
+ this.commit({ ...this.state, railOffset: clamped });
346
+ this.write({ general: { ...this.general, railOffset: clamped } });
347
+ }
348
+ /** Route the user's cascade-card count to the Host document(写入当前风格的独立段) */
349
+ setCardCount(count) {
350
+ if (this.scope === undefined)
351
+ return;
352
+ this.commit({ ...this.state, cardCount: count });
353
+ this.write({ styles: this.stylePatch({ cardCount: count }) });
354
+ }
355
+ /** Route the user's card-content selection to the Host document(写入当前风格的独立段) */
356
+ setCardItems(items) {
357
+ if (this.scope === undefined)
358
+ return;
359
+ this.commit({ ...this.state, cardItems: items });
360
+ this.write({ styles: this.stylePatch({ cardItems: items }) });
361
+ }
362
+ /** Route the user's mark-tone choice to the Host document(codex 刻度色调;写入当前风格段) */
363
+ setMarkTone(tone) {
364
+ if (this.scope === undefined)
365
+ return;
366
+ this.commit({ ...this.state, markTone: tone });
367
+ this.write({ styles: this.stylePatch({ markTone: tone }) });
368
+ }
369
+ }
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // 共享虚拟滚动列表:可见窗口 ≤50 + 前后缓冲 24,hover/current 钉住(C-5)
3
+ import { useMemo } from 'react';
4
+ import { virtualWindow } from "../pure.js";
5
+ export function VirtualList(props) {
6
+ const { items, itemHeight, viewportHeight, scrollTop, pinned, overscan = 24 } = props;
7
+ const win = useMemo(() => virtualWindow(items.length, scrollTop, viewportHeight, itemHeight, overscan), [items.length, scrollTop, viewportHeight, itemHeight, overscan]);
8
+ const rows = [];
9
+ for (let i = win.start; i < win.end; i++) {
10
+ const item = items[i];
11
+ const k = props.keyOf(item, i);
12
+ // hover/current 钉住:若在窗口外仍渲染(固定位置)
13
+ const isPinned = pinned?.has(k) ?? false;
14
+ if (i < win.start || i >= win.end) {
15
+ if (!isPinned)
16
+ continue;
17
+ }
18
+ rows.push(_jsx("div", { className: "mgs-vitem", style: { position: 'absolute', top: i * itemHeight, height: itemHeight, left: 0, right: 0 }, children: props.renderItem(item, i) }, k));
19
+ }
20
+ return (_jsx("div", { className: "mgs-vlist", ref: props.listRef, tabIndex: 0, role: "listbox", "aria-label": "messages", style: { position: 'relative', height: viewportHeight, overflowY: 'auto' }, onScroll: (e) => props.onScrollTopChange(e.target.scrollTop), onKeyDown: props.onKeyDown, children: _jsx("div", { style: { position: 'relative', height: items.length * itemHeight }, children: rows }) }));
21
+ }