minecodex 2.0.21 → 2.0.22

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.
@@ -6,6 +6,23 @@ import { installMarkdownToc } from './toc.mjs';
6
6
  import { loadNativeMarkdownTableActions, installMarkdownTableActions } from './table-actions.mjs';
7
7
  import { loadNativeMarkdownRichText, installMarkdownRichText } from './rich-text.mjs';
8
8
 
9
+ // 每个 Preview 共用换行状态,虚拟滚动重挂载的块也读取当前值。
10
+ export function createMarkdownWrapState() {
11
+ let enabled = false;
12
+ const listeners = new Set();
13
+ return {
14
+ get: () => enabled,
15
+ toggle() {
16
+ enabled = !enabled;
17
+ for (const listener of listeners) listener();
18
+ },
19
+ subscribe(listener) {
20
+ listeners.add(listener);
21
+ return () => listeners.delete(listener);
22
+ },
23
+ };
24
+ }
25
+
9
26
  // 原生能力加载与会话寿命归 Adapter;入口只持有检查结果和释放动作。
10
27
  export function createMarkdownReadingRuntime(
11
28
  codex,
@@ -37,6 +54,7 @@ export function createMarkdownReadingRuntime(
37
54
  }
38
55
  function attach({ editor, content, scroller }) {
39
56
  const view = content.cmTile?.view;
57
+ const wrapState = createMarkdownWrapState();
40
58
  const states = new Map([
41
59
  ['markdown-reading-layout', 'loading'],
42
60
  ['markdown-reading-links', 'loading'],
@@ -71,7 +89,7 @@ export function createMarkdownReadingRuntime(
71
89
  await stage('markdown-reading-rich-text', 'native-rich-text-unavailable', async () => {
72
90
  const rich = await load('loadNativeMarkdownRichText', actions);
73
91
  if (!current()) return;
74
- cleanups.push(capabilities.installMarkdownRichText(view, api, rich, codex));
92
+ cleanups.push(capabilities.installMarkdownRichText(view, api, rich, codex, wrapState));
75
93
  report('markdown-reading-rich-text', true);
76
94
  });
77
95
  }
@@ -102,7 +120,7 @@ export function createMarkdownReadingRuntime(
102
120
  const api = await capabilities.loadMarkdownListAPI(view);
103
121
  if (!current()) return;
104
122
  cleanups.push(capabilities.installMarkdownLists(view, api));
105
- const tables = capabilities.installMarkdownTables(view, api);
123
+ const tables = capabilities.installMarkdownTables(view, api, wrapState);
106
124
  cleanups.push(tables);
107
125
  report('markdown-reading-layout', true);
108
126
  await Promise.all([installLinks(api), installToc(api, tables)]);
@@ -38,7 +38,7 @@ export async function loadNativeMarkdownRichText(native) {
38
38
  return { ...native, CodeSnippet: sharedBinding(codeName), katex };
39
39
  }
40
40
 
41
- export function installMarkdownRichText(view, api, native, codex) {
41
+ export function installMarkdownRichText(view, api, native, codex, wrapState) {
42
42
  const { Decoration, StateField, StateEffect, Compartment, WidgetType, Prec } = api;
43
43
  const {
44
44
  React,
@@ -55,8 +55,7 @@ export function installMarkdownRichText(view, api, native, codex) {
55
55
  } = native;
56
56
  const h = React.createElement,
57
57
  roots = new Set(),
58
- footnoteReturns = new Map(),
59
- codeStates = new Map();
58
+ footnoteReturns = new Map();
60
59
  const withProviders = captureMarkdownProviders(view.dom, codex);
61
60
  const mount = (element, tree) => {
62
61
  const root = ReactDOM.createRoot(element);
@@ -64,11 +63,9 @@ export function installMarkdownRichText(view, api, native, codex) {
64
63
  root.render(withProviders(React, tree));
65
64
  return root;
66
65
  };
67
- function Code({ content, language, id }) {
68
- const [wrap, setWrap] = React.useState(
69
- () => (codeStates.get(id)?.source === content && codeStates.get(id).wrap) || false,
70
- ),
71
- [overflow, setOverflow] = React.useState(false),
66
+ function Code({ content, language }) {
67
+ const wrap = React.useSyncExternalStore(wrapState.subscribe, wrapState.get);
68
+ const [overflow, setOverflow] = React.useState(false),
72
69
  ref = React.useRef(null);
73
70
  React.useLayoutEffect(() => {
74
71
  const element = ref.current?.querySelector('code')?.parentElement;
@@ -84,7 +81,9 @@ export function installMarkdownRichText(view, api, native, codex) {
84
81
  check();
85
82
  return () => observer.disconnect();
86
83
  }, [wrap, content]);
87
- const label = wrap ? '关闭代码换行' : '开启代码换行';
84
+ const label = document.documentElement.lang.startsWith('zh')
85
+ ? wrap ? '关闭文档换行' : '开启文档换行'
86
+ : wrap ? 'Disable document word wrap' : 'Enable document word wrap';
88
87
  return h(
89
88
  'div',
90
89
  { ref, className: 'mcx-reading-code-block group/app-widget' },
@@ -112,10 +111,7 @@ export function installMarkdownRichText(view, api, native, codex) {
112
111
  color: 'blockAction',
113
112
  radius: 'full',
114
113
  size: 'inputIcon',
115
- onClick: () => {
116
- codeStates.set(id, { source: content, wrap: !wrap });
117
- setWrap(!wrap);
118
- },
114
+ onClick: () => wrapState.toggle(),
119
115
  },
120
116
  h(wrap ? wrapIcons.disable : wrapIcons.enable),
121
117
  ),
@@ -159,7 +155,7 @@ export function installMarkdownRichText(view, api, native, codex) {
159
155
  else if (this.kind === 'code')
160
156
  element._mcxRoot = mount(
161
157
  element,
162
- h(Code, { content: this.source, language: this.options.language, id: this.options.from }),
158
+ h(Code, { content: this.source, language: this.options.language }),
163
159
  );
164
160
  else if (this.kind === 'math')
165
161
  element.innerHTML = katex.renderToString(this.source, {
@@ -136,11 +136,11 @@ export function installMarkdownTableActions(view, native, codex, controller) {
136
136
  const tokens = React.useMemo(() => (open ? lexer(item.source, undefined) : []), [open, item.source]);
137
137
  const wrap = chinese
138
138
  ? item.fit
139
- ? '恢复自然列宽'
140
- : '适配宽度并换行'
139
+ ? '关闭文档换行'
140
+ : '开启文档换行'
141
141
  : item.fit
142
- ? 'Use natural table width'
143
- : 'Fit table width and wrap';
142
+ ? 'Disable document word wrap'
143
+ : 'Enable document word wrap';
144
144
  const copy = chinese ? '复制表格' : 'Copy table';
145
145
  const expand = chinese ? '展开表格' : 'Expand table';
146
146
  const preview = chinese ? '表格预览' : 'Table preview';
@@ -186,7 +186,7 @@ export function installMarkdownTableActions(view, native, codex, controller) {
186
186
  color: 'blockAction',
187
187
  radius: 'full',
188
188
  size: 'inputIcon',
189
- onClick: () => controller.toggle(item.id),
189
+ onClick: () => controller.toggle(),
190
190
  },
191
191
  h(item.fit ? wrapIcons.disable : wrapIcons.enable),
192
192
  ),
@@ -1,5 +1,5 @@
1
1
  // 保留原生逐行表格及选区;每张表的行共享横向位置,不移动正文节点。
2
- export function installMarkdownTables(view, api) {
2
+ export function installMarkdownTables(view, api, wrapState) {
3
3
  const { Decoration, ViewPlugin, StateEffect, Compartment, WidgetType } = api;
4
4
  class EmptyCell extends WidgetType {
5
5
  eq() {
@@ -82,7 +82,6 @@ export function installMarkdownTables(view, api) {
82
82
  view.dom.append(layoutStyle);
83
83
  let measuredDoc = null;
84
84
  const tables = new Map();
85
- const fitted = new Set();
86
85
  let hovered = null;
87
86
  function inline(state, node, target) {
88
87
  let cursor = node.from;
@@ -155,9 +154,9 @@ export function installMarkdownTables(view, api) {
155
154
  const inset = (scroller.clientWidth - available) / 2;
156
155
  const breakout = Math.max(0, parseFloat(padding.paddingLeft) - inset);
157
156
  for (const [id, table] of tables) {
158
- table.style.inlineSize = fitted.has(id) ? '100%' : 'max-content';
157
+ table.style.inlineSize = wrapState.get() ? '100%' : 'max-content';
159
158
  for (const cell of table.querySelectorAll('td,th'))
160
- cell.style.overflowWrap = fitted.has(id) ? 'anywhere' : 'break-word';
159
+ cell.style.overflowWrap = wrapState.get() ? 'anywhere' : 'break-word';
161
160
  }
162
161
  const widths = new Map(
163
162
  [...tables].map(([id, table]) => [
@@ -173,7 +172,7 @@ export function installMarkdownTables(view, api) {
173
172
  );
174
173
  columns.forEach((width, index) =>
175
174
  rules.push(
176
- `${selector} > .cm-markdown-table-cell:nth-child(${index + 1} of .cm-markdown-table-cell){width:${width}px;overflow-wrap:${fitted.has(id) ? 'anywhere' : 'break-word'}}`,
175
+ `${selector} > .cm-markdown-table-cell:nth-child(${index + 1} of .cm-markdown-table-cell){width:${width}px;overflow-wrap:${wrapState.get() ? 'anywhere' : 'break-word'}}`,
177
176
  ),
178
177
  );
179
178
  }
@@ -322,7 +321,7 @@ export function installMarkdownTables(view, api) {
322
321
  const headerInset = parseFloat(getComputedStyle(rows[0]).paddingTop);
323
322
  items.push({
324
323
  id,
325
- fit: fitted.has(id),
324
+ fit: wrapState.get(),
326
325
  overflow: (scrollLimits.get(id) ?? 0) > 0,
327
326
  visible: hovered === id,
328
327
  left: (first.left - frameRect.left) / scale,
@@ -435,7 +434,6 @@ export function installMarkdownTables(view, api) {
435
434
  update(update) {
436
435
  if (update.docChanged) {
437
436
  offsets.clear();
438
- fitted.clear();
439
437
  geometryRows = [];
440
438
  }
441
439
  if (update.docChanged || update.startState.tree !== update.state.tree) measuredDoc = null;
@@ -456,16 +454,16 @@ export function installMarkdownTables(view, api) {
456
454
  const controller = {
457
455
  schedule,
458
456
  renderActions: null,
459
- toggle(id) {
460
- if (fitted.has(id)) fitted.delete(id);
461
- else fitted.add(id);
462
- offsets.delete(id);
463
- schedule();
464
- view.requestMeasure();
465
- },
457
+ toggle: () => wrapState.toggle(),
466
458
  };
459
+ const unsubscribeWrap = wrapState.subscribe(() => {
460
+ offsets.clear();
461
+ schedule();
462
+ view.requestMeasure();
463
+ });
467
464
  schedule();
468
465
  const dispose = () => {
466
+ unsubscribeWrap();
469
467
  view.dom.removeEventListener('pointerover', onHover);
470
468
  view.dom.removeEventListener('pointerout', onHover);
471
469
  view.dom.removeEventListener('focusin', schedule);
@@ -3,6 +3,7 @@ import { collectMarkdownFootnotes } from './domain/footnotes.mjs';
3
3
  import { captureMarkdownProviders } from './adapters/codex/providers.mjs';
4
4
  import {
5
5
  createMarkdownReadingRuntime,
6
+ createMarkdownWrapState,
6
7
  installMarkdownMetadataScroll,
7
8
  } from './adapters/codex/reading-runtime.mjs';
8
9
  import { loadNativeMarkdownRichText, installMarkdownRichText } from './adapters/codex/rich-text.mjs';
@@ -23,6 +24,7 @@ export async function createPageScriptSource() {
23
24
  collectMarkdownFootnotes,
24
25
  captureMarkdownProviders,
25
26
  createMarkdownReadingRuntime,
27
+ createMarkdownWrapState,
26
28
  createMarkdownPreviewAdapter,
27
29
  installMarkdownMetadataScroll,
28
30
  loadMarkdownListAPI,
@@ -1331,18 +1331,14 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1331
1331
  particles.setAttribute("aria-hidden", "true");
1332
1332
  particles.setAttribute("data-animation-active", "true");
1333
1333
  const travelDuration = 1.9;
1334
- const stagger = travelDuration / 14;
1335
1334
  const valueMax = Number(rail.getAttribute("aria-valuemax") ?? 0);
1336
1335
  const valueNow = Number(rail.getAttribute("aria-valuenow") ?? 0);
1337
- const initialStartPercent = valueMax > 0 ? (valueNow / valueMax) * 100 : 0;
1338
- const remainingTravel = 1 - Math.min(Math.max(initialStartPercent, 0), 100) / 100;
1339
1336
  particles.append(...Array.from({ length: 14 }, (_, index) => {
1340
- const speed = 1 + (nativeParticleSeed(index, 21) - 0.5) * 0.4;
1341
- const duration = travelDuration / speed;
1342
1337
  const path = document.createElement("span");
1343
1338
  path.className = pathClass;
1344
- path.style.animationDelay = `${index * stagger - duration * remainingTravel}s`;
1345
- path.style.animationDuration = `${duration}s`;
1339
+ // 等速、等间距铺满完整路径;档位只裁剪可见范围,不压缩运动或聚集粒子。
1340
+ path.style.animationDelay = `${-((index + 0.5) / 14) * travelDuration}s`;
1341
+ path.style.animationDuration = `${travelDuration}s`;
1346
1342
  path.style.top = `${12 + nativeParticleSeed(index, 23) * 76}%`;
1347
1343
  const particle = document.createElement("span");
1348
1344
  particle.className = particleClass;
@@ -1354,6 +1350,8 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1354
1350
  clip.append(particles);
1355
1351
  const tickRail = Array.from(track.children).find((element) => String(element.className).includes("_TickRail_"));
1356
1352
  track.insertBefore(clip, tickRail ?? null);
1353
+ // 重建粒子层后按当前档位恢复裁剪,避免重开面板时覆盖整条滑轨。
1354
+ setGenericSliderVisual(rail, valueNow, valueMax + 1);
1357
1355
  if (entering) {
1358
1356
  // 首帧沿用关闭 Fast 时的星点,再渐显流动粒子。
1359
1357
  clip.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 240, easing: "ease-out" });
@@ -1381,7 +1379,6 @@ export function installModelSlider(signal, { feature, ui, createIcon, requestRef
1381
1379
  rail.setAttribute("data-fast-mode", String(enabled));
1382
1380
  rail.setAttribute("data-fast-mode-dot-transition", animate ? (enabled ? "entering" : "exiting") : (enabled ? "active" : "inactive"));
1383
1381
  syncNativeFastParticles(rail, enabled, animate && enabled);
1384
- setGenericSliderVisual(rail, Number(rail.getAttribute("aria-valuenow") ?? 0), Number(rail.getAttribute("aria-valuemax") ?? 0) + 1);
1385
1382
  if (animate && enabled) {
1386
1383
  shell.setAttribute("data-codex-model-slider-fast-transitioning", "");
1387
1384
  setTimeout(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecodex",
3
- "version": "2.0.21",
3
+ "version": "2.0.22",
4
4
  "description": "Lightweight, local-first plugins for the Codex desktop app.",
5
5
  "license": "MIT",
6
6
  "type": "module",