minecodex 2.0.11 → 2.0.13

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 (60) hide show
  1. package/README.md +12 -0
  2. package/apps/dashboard/app.js +37 -4
  3. package/apps/dashboard/assets/data-transfer.js +129 -0
  4. package/apps/dashboard/assets/download.svg +15 -0
  5. package/apps/dashboard/assets/upload.svg +15 -0
  6. package/apps/dashboard/index.html +10 -0
  7. package/apps/dashboard/styles.css +52 -1
  8. package/features/file-navigation/assets/book-open.svg +16 -0
  9. package/features/file-navigation/codex-feature.json +27 -0
  10. package/features/file-navigation/src/adapters/codex/modules.mjs +67 -0
  11. package/features/file-navigation/src/adapters/codex/navigation.mjs +84 -0
  12. package/features/file-navigation/src/domain/navigation.mjs +12 -0
  13. package/features/file-navigation/src/entry.mjs +34 -0
  14. package/features/file-navigation/src/frontend/navigation.mjs +140 -0
  15. package/features/file-navigation/src/injection-source.mjs +9 -0
  16. package/features/file-navigation/src/navigation.css +16 -0
  17. package/features/file-navigation/vendor/lucide-LICENSE +43 -0
  18. package/features/markdown-reading/assets/book-open.svg +16 -0
  19. package/features/markdown-reading/codex-feature.json +27 -0
  20. package/features/markdown-reading/src/adapters/codex/links.mjs +280 -0
  21. package/features/markdown-reading/src/adapters/codex/lists.mjs +168 -0
  22. package/features/markdown-reading/src/adapters/codex/markdown-native.mjs +28 -0
  23. package/features/markdown-reading/src/adapters/codex/preview.mjs +38 -0
  24. package/features/markdown-reading/src/adapters/codex/providers.mjs +18 -0
  25. package/features/markdown-reading/src/adapters/codex/reading-runtime.mjs +152 -0
  26. package/features/markdown-reading/src/adapters/codex/rich-text.mjs +561 -0
  27. package/features/markdown-reading/src/adapters/codex/table-actions.mjs +270 -0
  28. package/features/markdown-reading/src/adapters/codex/tables.mjs +493 -0
  29. package/features/markdown-reading/src/adapters/codex/toc-native.mjs +131 -0
  30. package/features/markdown-reading/src/adapters/codex/toc.mjs +334 -0
  31. package/features/markdown-reading/src/domain/footnotes.mjs +56 -0
  32. package/features/markdown-reading/src/injection-source.mjs +43 -0
  33. package/features/markdown-reading/src/markdown-reading.mjs +82 -0
  34. package/features/markdown-reading/src/reading.css +346 -0
  35. package/features/markdown-reading/vendor/lucide-LICENSE +43 -0
  36. package/features/model-slider/codex-feature.json +1 -0
  37. package/features/model-slider/src/adapters/codex/controller.mjs +7 -5
  38. package/features/model-slider/src/backend/favorites.mjs +54 -0
  39. package/features/model-slider/src/domain/model-rules.mjs +6 -1
  40. package/features/model-slider/src/frontend/install.mjs +41 -18
  41. package/features/notes/assets/prompt-previews.json +26 -0
  42. package/features/notes/codex-feature.json +2 -1
  43. package/features/notes/src/backend/transfer.mjs +97 -0
  44. package/features/notes/src/frontend/entry.mjs +380 -10
  45. package/features/notes/src/frontend/icons.json +4 -1
  46. package/features/notes/src/frontend/styles.css +63 -0
  47. package/features/session-status/src/domain/session-stats.mjs +22 -8
  48. package/features/session-status/src/session-status.mjs +2 -2
  49. package/features/sites/src/backend/sites-service.mjs +20 -5
  50. package/features/sites/src/backend/transfer.mjs +43 -0
  51. package/package.json +11 -3
  52. package/packages/cli/src/config.mjs +4 -2
  53. package/packages/cli/src/control-server.mjs +23 -4
  54. package/packages/cli/src/data-transfer/service.mjs +42 -0
  55. package/packages/cli/src/runtime-manager.mjs +3 -0
  56. package/packages/runtime-host/src/codex-injection.mjs +1 -0
  57. package/packages/runtime-host/src/codex-runtime.mjs +11 -0
  58. package/packages/runtime-host/src/feature-registry.mjs +1 -0
  59. package/packages/ui/src/native/theme.mjs +22 -1
  60. package/packages/ui/src/native/tokens.mjs +4 -0
@@ -0,0 +1,346 @@
1
+ /* Pages 正文角色;仅标记的原生 Preview 生效。布局尺寸是本功能的面板适配规则。 */
2
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-link {
3
+ color: var(--color-text-accent, var(--color-token-text-link-foreground));
4
+ text-decoration: underline;
5
+ cursor: var(--cursor-interaction, pointer);
6
+ overflow-wrap: anywhere;
7
+ }
8
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-link-definition {
9
+ font-size: 0;
10
+ line-height: 0;
11
+ padding: 0;
12
+ min-height: 0;
13
+ }
14
+ .cm-editor[data-minecodex-markdown-reading] {
15
+ --file-editor-font-size: var(--mcx-text-document, 14px);
16
+ --mcx-reading-width: 800px;
17
+ --mcx-reading-gutter: clamp(16px, 4cqi, 40px);
18
+ --mcx-reading-table-reading-width: calc(var(--thread-content-max-width, 768px) - var(--padding-toolbar, 16px) * 2);
19
+ --mcx-reading-table-wide-margin: min(calc(var(--spacing, .25rem) * 16), max(0px, (100cqi - var(--mcx-reading-table-reading-width)) / 2));
20
+ container-type: inline-size;
21
+ }
22
+ .cm-editor[data-minecodex-markdown-reading] > .cm-scroller {
23
+ font-family: var(--mcx-font-content, sans-serif);
24
+ font-size: var(--mcx-text-document, 14px);
25
+ line-height: var(--mcx-leading-relaxed, 1.625);
26
+ padding-block: calc(24px + var(--mcx-reading-metadata-height, 0px)) 64px;
27
+ padding-inline: max(var(--mcx-reading-gutter), calc((100% - var(--mcx-reading-width)) / 2));
28
+ }
29
+ [data-mcx-reading-metadata-frame] {
30
+ position: relative;
31
+ min-height: 0;
32
+ overflow: hidden;
33
+ }
34
+ [data-mcx-reading-metadata-frame] > [data-mcx-reading-metadata] {
35
+ position: absolute;
36
+ inset: 0 0 auto;
37
+ z-index: 1;
38
+ max-height: none;
39
+ overflow: visible;
40
+ }
41
+ .cm-editor[data-minecodex-markdown-reading] > .cm-scroller > .cm-content {
42
+ font-family: inherit;
43
+ font-size: inherit;
44
+ line-height: inherit;
45
+ padding: 0;
46
+ }
47
+ .cm-editor[data-minecodex-markdown-reading] .cm-line {
48
+ padding-block: 0;
49
+ }
50
+ /* 空行继续占位,保留原生文档位置和光标契约;不插入或删除段落。 */
51
+ .cm-editor[data-minecodex-markdown-reading] .cm-line:has(> br:only-child) {
52
+ line-height: 1em;
53
+ }
54
+ .cm-editor[data-minecodex-markdown-reading] .cm-line:has(.file-editor-heading) {
55
+ padding-block-start: 1em;
56
+ }
57
+ .cm-editor[data-minecodex-markdown-reading] .cm-line:first-child:has(.file-editor-heading) {
58
+ padding-block-start: 0;
59
+ }
60
+ .cm-editor[data-minecodex-markdown-reading] .file-editor-heading {
61
+ font-weight: var(--mcx-font-weight-semibold, 600);
62
+ }
63
+ .cm-editor[data-minecodex-markdown-reading] .file-editor-heading-1 {
64
+ font-size: calc(var(--mcx-text-document, 14px) * 1.5);
65
+ line-height: calc(var(--mcx-text-document, 14px) * 2);
66
+ }
67
+ .cm-editor[data-minecodex-markdown-reading] .file-editor-heading-2 {
68
+ font-size: calc(var(--mcx-text-document, 14px) * 1.25);
69
+ line-height: calc(var(--mcx-text-document, 14px) * 1.75);
70
+ }
71
+ .cm-editor[data-minecodex-markdown-reading] .file-editor-heading-3 {
72
+ font-size: calc(var(--mcx-text-document, 14px) * 1.125);
73
+ line-height: calc(var(--mcx-text-document, 14px) * 1.75);
74
+ }
75
+ .cm-editor[data-minecodex-markdown-reading] :is(.file-editor-heading-4, .file-editor-heading-5, .file-editor-heading-6) {
76
+ font-size: var(--mcx-text-document, 14px);
77
+ line-height: calc(var(--mcx-text-document, 14px) * 1.5);
78
+ }
79
+ .cm-editor[data-minecodex-markdown-reading] .cm-markdown-list-item {
80
+ padding-block: .125em;
81
+ }
82
+ .cm-editor[data-minecodex-markdown-reading] .cm-markdown-blockquote {
83
+ padding-block: .25em;
84
+ }
85
+ /* 代码内部继续使用原生等宽与高亮,不随正文行高扩张。 */
86
+ .cm-editor[data-minecodex-markdown-reading] .cm-markdown-code-line {
87
+ line-height: 20px;
88
+ }
89
+ .cm-editor[data-minecodex-markdown-reading] .cm-markdown-code-line-first {
90
+ padding-block-start: 12px;
91
+ }
92
+ .cm-editor[data-minecodex-markdown-reading] .cm-markdown-code-line-last {
93
+ padding-block-end: 12px;
94
+ }
95
+ .cm-editor[data-minecodex-markdown-reading] .cm-markdown-table-cell {
96
+ padding: 8px 12px;
97
+ }
98
+ /* 原生分隔行必须保持零高,避免覆盖 CodeMirror 的表格语义装饰。 */
99
+ .cm-editor[data-minecodex-markdown-reading] .cm-markdown-table-separator {
100
+ padding: 0;
101
+ line-height: 0;
102
+ }
103
+ /* Pages 列表的标记列与正文列;装饰由原生 CodeMirror 持有。 */
104
+ .cm-editor[data-minecodex-markdown-reading] .cm-line.mcx-reading-list-row {
105
+ position: relative;
106
+ padding-inline-start: calc(var(--mcx-list-indent) + var(--mcx-list-marker-width));
107
+ padding-block: .125em;
108
+ }
109
+ /* 标记固定在首行的 1lh 容器中居中,不随整项的多行高度移动。 */
110
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-list-marker {
111
+ display: inline-flex;
112
+ inline-size: var(--mcx-list-marker-width);
113
+ position: absolute;
114
+ inset-inline-start: var(--mcx-list-indent);
115
+ inset-block-start: .125em;
116
+ block-size: 1lh;
117
+ justify-content: flex-start;
118
+ align-items: center;
119
+ color: inherit;
120
+ user-select: none;
121
+ }
122
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-list-marker:is(.mcx-reading-disc,.mcx-reading-circle,.mcx-reading-square)::before {
123
+ content: '';
124
+ inline-size: 5px;
125
+ block-size: 5px;
126
+ margin-inline-start: 7px;
127
+ align-self: center;
128
+ background: currentColor;
129
+ border-radius: 50%;
130
+ }
131
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-list-marker.mcx-reading-circle::before {
132
+ background: transparent;
133
+ border: 1px solid currentColor;
134
+ }
135
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-list-marker.mcx-reading-square::before { border-radius: 0; }
136
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-list-marker:is(.mcx-reading-task-empty,.mcx-reading-task-checked) {
137
+ color: var(--color-text-tertiary);
138
+ }
139
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-list-marker:is(.mcx-reading-task-empty,.mcx-reading-task-checked) > span {
140
+ box-sizing: border-box;
141
+ inline-size: 16px;
142
+ block-size: 16px;
143
+ border: .9px solid var(--color-text-tertiary);
144
+ border-radius: 2px;
145
+ display: inline-grid;
146
+ place-content: center;
147
+ }
148
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-list-marker.mcx-reading-task-checked > span { background: currentColor; border-color: currentColor; }
149
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-task-checked > span::before {
150
+ content: '';
151
+ inline-size: 4px;
152
+ block-size: 8px;
153
+ border-inline-end: 2px solid var(--color-text-inverse);
154
+ border-block-end: 2px solid var(--color-text-inverse);
155
+ transform: translateY(-1px) rotate(45deg);
156
+ }
157
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-task-done {
158
+ color: var(--color-text-tertiary);
159
+ text-decoration: line-through;
160
+ }
161
+
162
+ /* Pages 细网格与顶对齐;宽表格只在本表内浏览,各行横向位置由 Adapter 同步。 */
163
+ .cm-editor[data-minecodex-markdown-reading] .cm-line.cm-markdown-table-row.mcx-reading-table-row {
164
+ position: relative;
165
+ overflow-x: hidden;
166
+ scroll-behavior: auto;
167
+ animation-range: 1px calc(100% - 1px);
168
+ scrollbar-width: none;
169
+ border-block-start: 0;
170
+ border-inline-start: var(--border-width-hairline, .5px) solid transparent;
171
+ font-variant-numeric: tabular-nums;
172
+ font-weight: var(--font-weight-normal, 400);
173
+ font-feature-settings: "liga" 0;
174
+ align-items: stretch;
175
+ }
176
+ .cm-editor[data-minecodex-markdown-reading] .cm-line.cm-markdown-table-header.mcx-reading-table-row {
177
+ border-block-start: var(--border-width-hairline, .5px) solid transparent;
178
+ --mcx-table-top-inset: 16px;
179
+ }
180
+ /* 边框只包住单元格,顶部操作区和底部滚动条留白不属于表格。 */
181
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-row::before {
182
+ content: '';
183
+ position: absolute;
184
+ inset: var(--mcx-table-top-inset, 0px) 0 var(--mcx-table-bottom-inset, 0px);
185
+ border-inline-start: var(--border-width-hairline, .5px) solid var(--color-border);
186
+ pointer-events: none;
187
+ }
188
+ .cm-editor[data-minecodex-markdown-reading] .cm-markdown-table-header.mcx-reading-table-row::before {
189
+ border-block-start: var(--border-width-hairline, .5px) solid var(--color-border);
190
+ }
191
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-row::after {
192
+ content: '';
193
+ position: absolute;
194
+ inset: 0 auto auto 0;
195
+ width: var(--mcx-table-scroll-width, 0px);
196
+ height: 1px;
197
+ pointer-events: none;
198
+ }
199
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-row > span[contenteditable=false]:empty:not(.cm-markdown-table-cell) {
200
+ display: none;
201
+ }
202
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-row .cm-markdown-table-cell {
203
+ box-sizing: border-box;
204
+ flex: 0 0 auto;
205
+ white-space: break-spaces;
206
+ overflow-wrap: break-word;
207
+ word-break: normal;
208
+ border-inline-end: var(--border-width-hairline, .5px) solid var(--color-border);
209
+ border-block-end: 1px solid var(--color-border);
210
+ text-align: start;
211
+ }
212
+ .cm-editor[data-minecodex-markdown-reading] .cm-markdown-table-header .cm-markdown-table-cell {
213
+ font-weight: var(--mcx-font-weight-semibold, 600);
214
+ }
215
+ .cm-editor[data-minecodex-markdown-reading] .cm-line.mcx-reading-table-last {
216
+ padding-block-end: 12px;
217
+ --mcx-table-bottom-inset: 12px;
218
+ }
219
+
220
+ /* Pages full-width 表格:正文宽度不限制宽表,测量层不参与滚动和选区。 */
221
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-measure {
222
+ position: absolute;
223
+ width: calc(100cqi - var(--mcx-reading-table-wide-margin) * 2);
224
+ inset: 0 auto auto 0;
225
+ visibility: hidden;
226
+ pointer-events: none;
227
+ font-weight: var(--font-weight-normal, 400);
228
+ font-feature-settings: "liga" 0;
229
+ contain: layout style;
230
+ overflow: hidden;
231
+ font-family: var(--mcx-font-content, sans-serif);
232
+ font-size: var(--mcx-text-document, 14px);
233
+ line-height: var(--mcx-leading-relaxed, 1.625);
234
+ }
235
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-measure table {
236
+ inline-size: max-content;
237
+ min-inline-size: 100%;
238
+ max-inline-size: none;
239
+ border-collapse: separate;
240
+ border-spacing: 0;
241
+ border-inline-start: var(--border-width-hairline, .5px) solid var(--color-border);
242
+ font-variant-numeric: tabular-nums;
243
+ }
244
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-measure :is(td, th) {
245
+ padding: 8px 12px;
246
+ border-inline-end: var(--border-width-hairline, .5px) solid var(--color-border);
247
+ border-block-end: 1px solid var(--color-border);
248
+ white-space: break-spaces;
249
+ overflow-wrap: break-word;
250
+ word-break: normal;
251
+ vertical-align: top;
252
+ text-align: start;
253
+ }
254
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-measure th { font-weight: var(--mcx-font-weight-semibold, 600); }
255
+
256
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-scrollbar {
257
+ position: absolute;
258
+ height: 12px;
259
+ overflow-x: scroll;
260
+ overflow-y: hidden;
261
+ scrollbar-width: thin;
262
+ scrollbar-color: var(--color-text-tertiary) transparent;
263
+ z-index: 1;
264
+ }
265
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-scrollbar > div { height: 1px; }
266
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-measure code {
267
+ font-family: var(--font-mono);
268
+ font-size: .92em;
269
+ font-weight: 400;
270
+ padding: 1px 6px;
271
+ border-radius: 6px;
272
+ background: color-mix(in srgb, var(--color-background-primary-ghost-hover) 60%, var(--color-text) 6%);
273
+ box-decoration-break: clone;
274
+ overflow-wrap: anywhere;
275
+ word-break: break-word;
276
+ }
277
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-measure strong { font-weight: var(--mcx-font-weight-semibold, 600); }
278
+
279
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-scrollbar:focus-visible { outline: 2px solid var(--color-text); outline-offset: 1px; }
280
+
281
+ /* 滚动条跟随整张表的 hover;操作区与聚焦时可达。 */
282
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-scrollbar {
283
+ opacity: 0;
284
+ pointer-events: none;
285
+ }
286
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-scrollbar:is([data-visible=true], :hover, :focus-within) {
287
+ opacity: 1;
288
+ pointer-events: auto;
289
+ }
290
+ .cm-editor[data-minecodex-markdown-reading] [data-mcx-reading-table-actions] { position: absolute; inset: 0 auto auto 0; width: 100%; height: 0; pointer-events: none; z-index: 3; }
291
+ @media (hover:none) {
292
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-table-scrollbar { opacity:1;pointer-events:auto; }
293
+ }
294
+
295
+
296
+ /* 快捷操作放在表头上方,避免适配宽度时遮住最后一列标题。 */
297
+ /* 留白必须计入 CodeMirror 的行高,外边距会造成点击坐标累积偏移。 */
298
+ .cm-editor[data-minecodex-markdown-reading] .cm-markdown-table-header.mcx-reading-table-row { padding-block-start:16px; }
299
+
300
+ /* 语法标记行的隐藏不能留下正文行高。 */
301
+ .cm-editor[data-minecodex-markdown-reading] .cm-line.mcx-reading-syntax-line {
302
+ padding: 0; min-height: 0; height: 0; font-size: 0; line-height: 0;
303
+ }
304
+ .cm-editor[data-minecodex-markdown-reading] .cm-line.mcx-reading-quote {
305
+ border-inline-start: 0;
306
+ padding-inline-start: calc(var(--mcx-quote-indent) + 8px);
307
+ background-image: repeating-linear-gradient(to right, var(--color-border) 0 2px, transparent 2px 20px);
308
+ background-size: var(--mcx-quote-indent) 100%;
309
+ background-repeat: no-repeat;
310
+ color: var(--color-text-secondary);
311
+ }
312
+ .cm-editor[data-minecodex-markdown-reading] .cm-line.mcx-reading-quote.mcx-reading-list-row {
313
+ padding-inline-start: calc(var(--mcx-list-indent) + var(--mcx-list-marker-width));
314
+ }
315
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-alert-title {font-weight:var(--font-weight-semibold,600); color:var(--color-text-primary);}
316
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich-code {margin-block:16px; max-width:100%;}
317
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-block {position:relative; min-width:0;}
318
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-block [data-block-actions] {top:4px; right:4px; transform:none; transition:opacity 120ms linear;}
319
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-block:focus-within [data-block-actions],
320
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-block:hover [data-block-actions] {opacity:1; pointer-events:auto;}
321
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-scroll {max-width:100%; scrollbar-width:thin;}
322
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich-html-block {margin-block:1em;}
323
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich mark {background:color-mix(in srgb, var(--color-content-yellow) 25%, transparent); color:inherit; border-radius:2px; padding-inline:2px;}
324
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich kbd {font-family:var(--font-mono,monospace); font-size:.9em; background:var(--color-token-bg-secondary); border:1px solid var(--color-border); border-radius:4px; padding:1px 4px;}
325
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich a,
326
+ .cm-editor[data-minecodex-markdown-reading] :is(.mcx-reading-rich-footnote-ref,.mcx-reading-footnote-body) button {color:var(--color-text-accent, var(--color-token-text-link-foreground)); cursor:var(--cursor-interaction,pointer);}
327
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich-footnote-ref button {font:inherit; padding:0 2px; background:transparent; border:0;}
328
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-footnote-body {font-size:.9em; color:var(--color-text-secondary);}
329
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-footnote-body button {margin-inline-start:8px; background:transparent; border:0;}
330
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-definition-term {font-weight:var(--font-weight-semibold,600);}
331
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-definition-body {padding-inline-start:1.75em;}
332
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-footnote-content > div,
333
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich-html span > div {display:inline;}
334
+
335
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-scroll::-webkit-scrollbar {height:6px;}
336
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-scroll::-webkit-scrollbar-thumb {background:transparent; border-radius:99px;}
337
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-block:hover .mcx-reading-code-scroll::-webkit-scrollbar-thumb,
338
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-block:focus-within .mcx-reading-code-scroll::-webkit-scrollbar-thumb {background:var(--color-text-tertiary);}
339
+
340
+ .cm-editor[data-minecodex-markdown-reading] .cm-line.mcx-reading-callout {
341
+ background:var(--color-token-bg-secondary); padding-inline:16px; color:var(--color-text-primary);
342
+ }
343
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-callout-first {padding-block-start:12px; border-start-start-radius:12px; border-start-end-radius:12px;}
344
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-callout-last {padding-block-end:12px; border-end-start-radius:12px; border-end-end-radius:12px;}
345
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-footnote-content > div {font-size:inherit; line-height:inherit;}
346
+ .cm-editor[data-minecodex-markdown-reading] .mcx-reading-footnote-body button {font:inherit; vertical-align:baseline; padding:0;}
@@ -0,0 +1,43 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Lucide Icons and Contributors
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
+
17
+ ---
18
+
19
+ The following Lucide icons are derived from the Feather project:
20
+
21
+ airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
22
+
23
+ The MIT License (MIT) (for the icons listed above)
24
+
25
+ Copyright (c) 2013-present Cole Bemis
26
+
27
+ Permission is hereby granted, free of charge, to any person obtaining a copy
28
+ of this software and associated documentation files (the "Software"), to deal
29
+ in the Software without restriction, including without limitation the rights
30
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31
+ copies of the Software, and to permit persons to whom the Software is
32
+ furnished to do so, subject to the following conditions:
33
+
34
+ The above copyright notice and this permission notice shall be included in all
35
+ copies or substantial portions of the Software.
36
+
37
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
40
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
43
+ SOFTWARE.
@@ -13,6 +13,7 @@
13
13
  "entry": {
14
14
  "kind": "composer-model-selector"
15
15
  },
16
+ "hostActions": ["model-favorites"],
16
17
  "modelSelector": {
17
18
  "maxVisibleItems": 6,
18
19
  "favoriteStorageKey": "codex-model-slider:favorites:v1",
@@ -1,14 +1,16 @@
1
1
  export function createModelControllerAdapter(codex,comparableModelLabel) {
2
2
  function controllerView(props) {const view=Object.fromEntries(["models","model","reasoningEffort","powerSelections","selectedPowerSelection","fallbackPowerSelection","serviceTierOptions","selectedServiceTier","onSelectModel","onSelectReasoningEffort","onSelectPower","onCommitPower","onSelectServiceTier"].map(key=>[key,props[key]]));codex.report('model-controller',true);return view;}
3
- function nativeComposerTrigger() {
4
- const triggers = Array.from(document.querySelectorAll("[data-codex-intelligence-trigger]"))
3
+ function nativeComposerTriggers() {
4
+ return Array.from(document.querySelectorAll("[data-codex-intelligence-trigger]"))
5
5
  .filter((trigger) => trigger.checkVisibility({ checkVisibilityCSS: true, checkOpacity: true }));
6
+ }
7
+ function nativeComposerTrigger() {
8
+ const triggers = nativeComposerTriggers();
6
9
  return triggers.find((trigger) => trigger.getAttribute("data-state") === "open")
7
10
  ?? triggers[0] ?? null;
8
11
  }
9
- function nativeComposerModelController() {
12
+ function nativeComposerModelController(trigger = nativeComposerTrigger()) {
10
13
  codex.report("model-controller",false,"task-controller-unavailable");
11
- const trigger = nativeComposerTrigger();
12
14
  let fiber = codex.currentFiber(trigger);
13
15
  let powerController = null;
14
16
  for (let depth = 0; fiber && depth < 40; depth += 1, fiber = fiber.return) {
@@ -102,5 +104,5 @@ function selfCheck() {
102
104
  return {state:available ? "available" : "unavailable",reason:available ? null : "model-controller-unavailable",
103
105
  scopes:[{name:"model-trigger",element:trigger}],checks:[{name:"model-trigger",available:true},{name:"model-controller",available}]};
104
106
  }
105
- return {selfCheck,fastIcons,trigger:nativeComposerTrigger,read:nativeComposerModelController,descriptor:nativeModelDescriptorForItem,setOpen:setReactControlledMenuOpen,selectItem:invokeNativeMenuItemSelect};
107
+ return {selfCheck,fastIcons,trigger:nativeComposerTrigger,triggers:nativeComposerTriggers,read:nativeComposerModelController,descriptor:nativeModelDescriptorForItem,setOpen:setReactControlledMenuOpen,selectItem:invokeNativeMenuItemSelect};
106
108
  }
@@ -0,0 +1,54 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { existsSync, mkdirSync, chmodSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+
6
+ export function defaultFavoritesPath() {
7
+ const support = process.platform === 'win32' ? path.join(process.env.APPDATA || os.homedir(), 'MineCodex') : path.join(os.homedir(), 'Library', 'Application Support', 'MineCodex');
8
+ return process.env.MINECODEX_MODEL_DATA_FILE || path.join(support, 'model-slider', 'favorites.sqlite');
9
+ }
10
+ export function validateFavorites(data) {
11
+ if (!data || data.version !== 1 || !Array.isArray(data.favorites) || data.favorites.length > 10000 || data.favorites.some(id => typeof id !== 'string' || !id || id.length > 512 || /[\x00-\x1f]/.test(id))) throw new Error('Invalid model favorites');
12
+ return { version: 1, favorites: [...new Set(data.favorites)] };
13
+ }
14
+ export class ModelFavorites {
15
+ constructor(file = defaultFavoritesPath()) { this.file = file; }
16
+ read() {
17
+ if (!existsSync(this.file)) return { version: 1, favorites: [], initialized: false };
18
+ const db = new DatabaseSync(this.file, { readOnly: true });
19
+ try { return { version: 1, favorites: db.prepare('SELECT id FROM favorites ORDER BY id').all().map(row => row.id), initialized: Boolean(db.prepare("SELECT 1 FROM metadata WHERE key='initialized'").get()) }; }
20
+ finally { db.close(); }
21
+ }
22
+ action(payload) {
23
+ if (!payload || !['initialize','change','read','merge'].includes(payload.operation)) throw new Error('Unknown favorites operation');
24
+ if (payload.operation === 'read') return this.read();
25
+ const additions = validateFavorites({ version: 1, favorites: payload.add ?? [] }).favorites;
26
+ const removals = validateFavorites({ version: 1, favorites: payload.remove ?? [] }).favorites;
27
+ mkdirSync(path.dirname(this.file), { recursive: true, mode: 0o700 });
28
+ const db = new DatabaseSync(this.file);
29
+ chmodSync(this.file, 0o600);
30
+ try {
31
+ db.exec('PRAGMA busy_timeout=3000; CREATE TABLE IF NOT EXISTS favorites(id TEXT PRIMARY KEY); CREATE TABLE IF NOT EXISTS metadata(key TEXT PRIMARY KEY); BEGIN IMMEDIATE');
32
+ const before = new Set(db.prepare('SELECT id FROM favorites').all().map(row => row.id));
33
+ const initialized = db.prepare("SELECT 1 FROM metadata WHERE key='initialized'").get();
34
+ let added = 0;
35
+ if (payload.operation !== 'initialize' || !initialized) {
36
+ for (const id of additions) { if (!before.has(id)) added++; db.prepare('INSERT OR IGNORE INTO favorites VALUES (?)').run(id); }
37
+ if (payload.operation === 'change') for (const id of removals) db.prepare('DELETE FROM favorites WHERE id=?').run(id);
38
+ }
39
+ if (payload.operation === 'initialize') db.prepare("INSERT OR IGNORE INTO metadata VALUES ('initialized')").run();
40
+ const favorites = db.prepare('SELECT id FROM favorites ORDER BY id').all().map(row => row.id);
41
+ db.exec('COMMIT'); return { version: 1, favorites, added, duplicate: additions.length - added, conflict: 0 };
42
+ } catch (error) { if (db.isTransaction) db.exec('ROLLBACK'); throw error; }
43
+ finally { db.close(); }
44
+ }
45
+ }
46
+ export function favoritesTransfer(file) {
47
+ const store = new ModelFavorites(file);
48
+ return {
49
+ id: 'model-slider', validate: validateFavorites,
50
+ async snapshot() { const data = store.read(); return { data: { version: 1, favorites: data.favorites }, count: data.favorites.length, excluded: 0, initialized: data.initialized }; },
51
+ async preview(data) { const current = new Set(store.read().favorites); const added = data.favorites.filter(id => !current.has(id)).length; return { added, duplicate: data.favorites.length - added, conflict: 0 }; },
52
+ async apply(data) { const { added, duplicate, conflict } = store.action({ operation:'merge',add:data.favorites }); return { added, duplicate, conflict }; },
53
+ };
54
+ }
@@ -110,6 +110,11 @@ export function createModelRules() {
110
110
  return formatIdentifier(identity.backend).toLowerCase() === nativeLabel ? native : thirdParty;
111
111
  }
112
112
 
113
+ function favoriteModelId(raw, { authoritative = false, nativeSlug, catalogSlug } = {}) {
114
+ // 控制器 ID 和 provider/model 不经过显示名称别名匹配,跨机器只认同一完整标识。
115
+ return authoritative || modelIdentity(raw).provider ? raw : nativeSlug ?? catalogSlug ?? raw;
116
+ }
117
+
113
118
  function nativeGptSortRank(rawValue) {
114
119
  const known = new Map([
115
120
  ["gpt-6-astra", 1], ["gpt-5.6-sol", 2], ["gpt-5.6-terra", 3], ["gpt-5.6-luna", 4],
@@ -139,5 +144,5 @@ export function createModelRules() {
139
144
  );
140
145
  });
141
146
  }
142
- return { modelIdentity, formatIdentifier, providerLabel, compactModelLabel, brandForModel, comparableModelLabel, catalogModelFor, resolveModelAlias, modelWithOpenCodexEfforts, nativeGptSortRank, visibleControllerModels };
147
+ return { favoriteModelId, modelIdentity, formatIdentifier, providerLabel, compactModelLabel, brandForModel, comparableModelLabel, catalogModelFor, resolveModelAlias, modelWithOpenCodexEfforts, nativeGptSortRank, visibleControllerModels };
143
148
  }
@@ -1,8 +1,8 @@
1
1
  // 专有原生控制器适配与呈现由插件维护;宿主只提供刷新和共用 UI。
2
- export function installModelSlider(signal, { feature, ui, createIcon, requestRefresh, locale, codex }, rules, createModelControllerAdapter) {
2
+ export function installModelSlider(signal, { feature, ui, createIcon, requestRefresh, locale, codex, requestAction }, rules, createModelControllerAdapter) {
3
3
  const modelSelectorFeature = feature;
4
4
  const featureUI = ui, createMarkupIcon = createIcon, queueEnsure = requestRefresh;
5
- const { modelIdentity, formatIdentifier, providerLabel, compactModelLabel, brandForModel, comparableModelLabel, catalogModelFor, resolveModelAlias, modelWithOpenCodexEfforts, nativeGptSortRank, visibleControllerModels } = rules;
5
+ const { favoriteModelId, modelIdentity, formatIdentifier, providerLabel, compactModelLabel, brandForModel, comparableModelLabel, catalogModelFor, resolveModelAlias, modelWithOpenCodexEfforts, nativeGptSortRank, visibleControllerModels } = rules;
6
6
  const modelSelectorOriginalToggleLabels = new Map();
7
7
  const modelSelectorGenericContainers = new Map();
8
8
  const modelSelectorNativeToggleHandlers = new Map();
@@ -13,8 +13,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
13
13
  let modelSelectorEffortTransactionTimer = null;
14
14
  let modelSelectorPendingOutsideDismiss = false;
15
15
  let modelSelectorSuppressOutsidePointerSequence = false;
16
- let modelSelectorTriggerObserver = null;
17
- let modelSelectorObservedTrigger = null;
16
+ const modelSelectorTriggerObservers = new Map();
18
17
  let modelSelectorControllerMenu = null;
19
18
  let modelSelectorNativeFastIcons = null;
20
19
  // 持久化脏标记:仅在图标实际变化时写一次 localStorage。
@@ -44,7 +43,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
44
43
  }
45
44
  html[data-codex-model-slider-catalog-ready]
46
45
  [data-codex-model-slider-trigger]
47
- [class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label]) {
46
+ [class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label]):has(+ [data-codex-model-slider-trigger-label]) {
48
47
  display: none !important;
49
48
  }
50
49
  [data-codex-model-slider-trigger-label] {
@@ -375,7 +374,17 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
375
374
  }
376
375
  }
377
376
 
377
+ let favoritesWrite = Promise.resolve();
378
+ let favoritesEpoch = 0;
378
379
  function saveModelFavorites(selector, favorites) {
380
+ const previous = loadModelFavorites(selector);
381
+ favoritesEpoch++;
382
+ if (requestAction) {
383
+ const add = [...favorites].filter(id => !previous.has(id));
384
+ const remove = [...previous].filter(id => !favorites.has(id));
385
+ favoritesWrite = favoritesWrite.catch(() => {}).then(() => requestAction({ operation: 'change', add, remove }));
386
+ void favoritesWrite.catch(error => console.warn('Model favorites could not be saved for transfer', error));
387
+ }
379
388
  try {
380
389
  localStorage.setItem(selector.favoriteStorageKey, JSON.stringify(Array.from(favorites)));
381
390
  } catch {
@@ -734,7 +743,7 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
734
743
  const observedIdentity = modelIdentity(observedRaw);
735
744
  const nativeDescriptor = nativeModelDescriptorForItem(item, observedRaw);
736
745
  const catalogModel = catalogModelFor(observedIdentity, selector);
737
- const raw = catalogModel?.slug ?? nativeDescriptor?.slug ?? observedRaw;
746
+ const raw = favoriteModelId(observedRaw, { authoritative: rawValue !== null, nativeSlug: nativeDescriptor?.slug, catalogSlug: catalogModel?.slug });
738
747
  // 先刷新 dataset:React 原地复用菜单节点时,raw/排序/收藏键不能基于过期值。
739
748
  item.dataset.codexModelSliderRaw = raw;
740
749
  item.dataset.codexModelSliderItem = "";
@@ -1990,14 +1999,11 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1990
1999
  return /^(select\s+(model|effort)|选择\s*(模型|思考强度))$/i.test(String(value ?? "").trim());
1991
2000
  }
1992
2001
 
1993
- function enhanceComposerModelTrigger(selector) {
1994
- const trigger = nativeComposerTrigger();
1995
- if (!trigger) return;
1996
- if (modelSelectorObservedTrigger !== trigger) {
1997
- modelSelectorTriggerObserver?.disconnect();
1998
- modelSelectorObservedTrigger = trigger;
1999
- modelSelectorTriggerObserver = new MutationObserver(queueEnsure);
2000
- modelSelectorTriggerObserver.observe(trigger, { childList: true, subtree: true, characterData: true });
2002
+ function enhanceComposerModelTrigger(trigger, selector) {
2003
+ if (!modelSelectorTriggerObservers.has(trigger)) {
2004
+ const observer = new MutationObserver(queueEnsure);
2005
+ observer.observe(trigger, { childList: true, subtree: true, characterData: true });
2006
+ modelSelectorTriggerObservers.set(trigger, observer);
2001
2007
  }
2002
2008
  const nativeLabels = Array.from(trigger.querySelectorAll(
2003
2009
  '[class*="_ModelPickerTriggerModelText_"]:not([data-codex-model-slider-trigger-label])',
@@ -2060,7 +2066,14 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2060
2066
  pruneGenericModelPickerContainers();
2061
2067
  document.documentElement.setAttribute("data-codex-model-slider-catalog-ready", "");
2062
2068
  if (!modelSelectorStyle.isConnected) document.head?.append(modelSelectorStyle);
2063
- enhanceComposerModelTrigger(selector);
2069
+ // 主会话与 side chat 同时维护标签;菜单动作仍跟随原生打开的按钮。
2070
+ const triggers = nativeModel.triggers().filter(trigger => nativeModel.read(trigger));
2071
+ for (const [trigger, observer] of modelSelectorTriggerObservers) {
2072
+ if (triggers.includes(trigger)) continue;
2073
+ observer.disconnect();
2074
+ modelSelectorTriggerObservers.delete(trigger);
2075
+ }
2076
+ for (const trigger of triggers) enhanceComposerModelTrigger(trigger, selector);
2064
2077
  const parentMenu = nativeModelPickerMenu();
2065
2078
  if (modelSelectorControllerMenu && modelSelectorControllerMenu.parentMenu !== parentMenu) {
2066
2079
  closeControllerModelMenu({ immediate: true });
@@ -2092,7 +2105,8 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2092
2105
  closeControllerModelMenu({ immediate: true });
2093
2106
  clearTimeout(modelSelectorReturnTimer);
2094
2107
  clearTimeout(modelSelectorEffortTransactionTimer);
2095
- modelSelectorTriggerObserver?.disconnect();
2108
+ for (const observer of modelSelectorTriggerObservers.values()) observer.disconnect();
2109
+ modelSelectorTriggerObservers.clear();
2096
2110
  for (const record of modelSelectorPositionObservers.values()) {
2097
2111
  record.observer.disconnect();
2098
2112
  record.resizeObserver.disconnect();
@@ -2100,8 +2114,6 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2100
2114
  modelSelectorPositionObservers.clear();
2101
2115
  for (const frame of modelSelectorMenuSizeFrames.values()) cancelAnimationFrame(frame);
2102
2116
  modelSelectorMenuSizeFrames.clear();
2103
- modelSelectorTriggerObserver = null;
2104
- modelSelectorObservedTrigger = null;
2105
2117
  modelSelectorReturnTimer = null;
2106
2118
  modelSelectorEffortTransactionTimer = null;
2107
2119
  modelSelectorPendingOutsideDismiss = false;
@@ -2163,6 +2175,17 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
2163
2175
  && !modelSelectorControllerMenu.menu.contains(event.target)
2164
2176
  && !modelSelectorControllerMenu.button.contains(event.target)) closeControllerModelMenu();
2165
2177
  }, {capture:true, signal});
2178
+ if (modelSelectorFeature && requestAction) {
2179
+ const selector = modelSelectorFeature.modelSelector;
2180
+ const legacy = [...loadModelFavorites(selector)];
2181
+ // 仅首次接入时迁移旧收藏;后续以后端为准,避免取消收藏后被旧窗口恢复。
2182
+ favoritesWrite = requestAction({ operation: 'initialize', add: legacy }).then(result => {
2183
+ if (signal.aborted) return;
2184
+ if (favoritesEpoch === 0) localStorage.setItem(selector.favoriteStorageKey, JSON.stringify(result.favorites));
2185
+ queueEnsure();
2186
+ });
2187
+ void favoritesWrite.catch(error => console.warn('Model favorites transfer store unavailable', error));
2188
+ }
2166
2189
  if (modelSelectorFeature) {
2167
2190
  // 启动阶段就解析 Fast 图标:先读持久化的原生克隆,再从 app bundle 静态提取兜底,
2168
2191
  // 避免首次会话必须等用户点开弹窗后 DOM 里才出现图标。