minecodex 2.0.12 → 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 (59) 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/backend/favorites.mjs +54 -0
  38. package/features/model-slider/src/domain/model-rules.mjs +6 -1
  39. package/features/model-slider/src/frontend/install.mjs +24 -3
  40. package/features/notes/assets/prompt-previews.json +26 -0
  41. package/features/notes/codex-feature.json +2 -1
  42. package/features/notes/src/backend/transfer.mjs +97 -0
  43. package/features/notes/src/frontend/entry.mjs +380 -10
  44. package/features/notes/src/frontend/icons.json +4 -1
  45. package/features/notes/src/frontend/styles.css +63 -0
  46. package/features/session-status/src/domain/session-stats.mjs +22 -8
  47. package/features/session-status/src/session-status.mjs +2 -2
  48. package/features/sites/src/backend/sites-service.mjs +20 -5
  49. package/features/sites/src/backend/transfer.mjs +43 -0
  50. package/package.json +11 -3
  51. package/packages/cli/src/config.mjs +4 -2
  52. package/packages/cli/src/control-server.mjs +23 -4
  53. package/packages/cli/src/data-transfer/service.mjs +42 -0
  54. package/packages/cli/src/runtime-manager.mjs +3 -0
  55. package/packages/runtime-host/src/codex-injection.mjs +1 -0
  56. package/packages/runtime-host/src/codex-runtime.mjs +11 -0
  57. package/packages/runtime-host/src/feature-registry.mjs +1 -0
  58. package/packages/ui/src/native/theme.mjs +22 -1
  59. package/packages/ui/src/native/tokens.mjs +4 -0
@@ -0,0 +1,334 @@
1
+ import { captureMarkdownProviders } from './providers.mjs';
2
+
3
+ // Markdown 负责标题与导航;原生组件负责短线、动效、浮层材质与悬浮定位。
4
+ export function installMarkdownToc(view, api, native, codex) {
5
+ const { React, ReactDOM, Marker, Panel, Tooltip, classes, indentation } = native;
6
+ const h = React.createElement;
7
+ const compartment = new api.Compartment();
8
+ const controller = new AbortController(),
9
+ { signal } = controller;
10
+ const mount = document.createElement('div');
11
+ const panel = view.dom.closest('[data-app-shell-tab-panel-controller]');
12
+ mount.dataset.mcxReadingToc = '';
13
+ mount.style.display = 'contents';
14
+ view.dom.append(mount);
15
+ const withProviders = captureMarkdownProviders(view.dom, codex);
16
+ const root = ReactDOM.createRoot(mount);
17
+ let headings = [],
18
+ active = null,
19
+ visible = false,
20
+ frame = null,
21
+ navigationFrame = null,
22
+ navigating = false,
23
+ scrollTimer = null;
24
+ const scrollBehavior = view.scrollDOM.style.scrollBehavior;
25
+ function restoreScroll() {
26
+ clearTimeout(scrollTimer);
27
+ if (navigationFrame !== null) cancelAnimationFrame(navigationFrame);
28
+ navigationFrame = null;
29
+ view.scrollDOM.style.scrollBehavior = scrollBehavior;
30
+ }
31
+ function navigate(heading, behavior) {
32
+ restoreScroll();
33
+ active = heading.id;
34
+ navigating = true;
35
+ render();
36
+ const start = view.scrollDOM.scrollTop;
37
+ view.scrollDOM.style.scrollBehavior = 'auto';
38
+ view.dispatch({ effects: view.constructor.scrollIntoView(heading.from, { y: 'start', yMargin: 24 }) });
39
+ if (behavior !== 'instant' && !matchMedia('(prefers-reduced-motion: reduce)').matches) {
40
+ navigationFrame = requestAnimationFrame(() => {
41
+ navigationFrame = requestAnimationFrame(() => {
42
+ navigationFrame = null;
43
+ const destination = view.scrollDOM.scrollTop;
44
+ view.scrollDOM.scrollTo({ top: start, behavior: 'instant' });
45
+ view.scrollDOM.scrollTo({ top: destination, behavior: 'smooth' });
46
+ });
47
+ });
48
+ }
49
+ scrollTimer = setTimeout(() => {
50
+ restoreScroll();
51
+ navigating = false;
52
+ schedule();
53
+ }, 1200);
54
+ }
55
+ function reveal(container, item) {
56
+ if (!container || !item) return;
57
+ let top = item.offsetTop;
58
+ for (
59
+ let parent = item.offsetParent;
60
+ parent instanceof HTMLElement && parent !== container && parent !== container.offsetParent;
61
+ parent = parent.offsetParent
62
+ )
63
+ top += parent.offsetTop;
64
+ if (top < container.scrollTop) container.scrollTop = top;
65
+ else if (top + item.offsetHeight > container.scrollTop + container.clientHeight)
66
+ container.scrollTop = top + item.offsetHeight - container.clientHeight + 1;
67
+ }
68
+ function Contents({ headings, active }) {
69
+ const [preview, setPreview] = React.useState(null),
70
+ [scrub, setScrub] = React.useState(null);
71
+ const rail = React.useRef(null),
72
+ list = React.useRef(null),
73
+ pointer = React.useRef(null),
74
+ dragged = React.useRef(false);
75
+ React.useEffect(() => {
76
+ if (!pointer.current)
77
+ reveal(rail.current, rail.current?.querySelector(`[data-page-heading-id="${preview ?? active}"]`));
78
+ }, [preview, active]);
79
+ function end(event) {
80
+ const p = pointer.current;
81
+ if (p?.pointerId !== event.pointerId) return;
82
+ pointer.current = null;
83
+ setScrub(null);
84
+ if (p.target.hasPointerCapture?.(event.pointerId)) p.target.releasePointerCapture(event.pointerId);
85
+ if (event.type === 'pointercancel' || event.type === 'lostpointercapture') dragged.current = false;
86
+ }
87
+ function move(event) {
88
+ const p = pointer.current;
89
+ if (!p || p.pointerId !== event.pointerId) return;
90
+ if (event.buttons % 2 === 0) {
91
+ end(event);
92
+ return;
93
+ }
94
+ const element = event.currentTarget,
95
+ rect = element.getBoundingClientRect();
96
+ const button = document
97
+ .elementFromPoint(
98
+ rect.left + rect.width / 2,
99
+ Math.max(rect.top, Math.min(event.clientY, rect.bottom - 1)),
100
+ )
101
+ ?.closest('[data-page-heading-id]');
102
+ if (!button || !element.contains(button)) return;
103
+ const heading = headings.find((h) => h.id === button.dataset.pageHeadingId);
104
+ if (heading && heading.id !== p.headingId) {
105
+ p.headingId = heading.id;
106
+ dragged.current = true;
107
+ setScrub(heading.id);
108
+ setPreview(heading.id);
109
+ navigate(heading, 'instant');
110
+ }
111
+ }
112
+ const railTree = h(
113
+ 'div',
114
+ {
115
+ ref: rail,
116
+ className: classes.rail,
117
+ 'data-floating-navigation-rail-list': true,
118
+ 'data-scrubbing': scrub != null || undefined,
119
+ onPointerMove: move,
120
+ onPointerUp: end,
121
+ onPointerCancel: end,
122
+ onLostPointerCapture: end,
123
+ },
124
+ headings.map((heading, index) =>
125
+ h(
126
+ 'button',
127
+ {
128
+ key: heading.id,
129
+ type: 'button',
130
+ className: classes.railButton,
131
+ 'aria-current': active === heading.id ? 'true' : undefined,
132
+ 'aria-label': `Jump to heading ${index + 1}: ${heading.title}`,
133
+ 'data-page-heading-id': heading.id,
134
+ 'data-scrub-target': (scrub ?? preview) === heading.id || undefined,
135
+ onPointerDown: (event) => {
136
+ if (event.button === 0 && !pointer.current) {
137
+ dragged.current = false;
138
+ pointer.current = {
139
+ pointerId: event.pointerId,
140
+ target: event.currentTarget,
141
+ headingId: heading.id,
142
+ };
143
+ setScrub(heading.id);
144
+ event.currentTarget.setPointerCapture?.(event.pointerId);
145
+ }
146
+ },
147
+ onClick: (event) => {
148
+ if (dragged.current && event.detail !== 0) {
149
+ dragged.current = false;
150
+ return;
151
+ }
152
+ navigate(heading);
153
+ },
154
+ onPointerEnter: () => setPreview(heading.id),
155
+ onFocus: () => setPreview(heading.id),
156
+ },
157
+ h(Marker, { hierarchyLevel: Math.min(heading.level, 3) }),
158
+ ),
159
+ ),
160
+ );
161
+ const current = preview ?? active;
162
+ const listTree = h(
163
+ Panel,
164
+ {
165
+ ref: (element) => {
166
+ list.current = element;
167
+ reveal(element, element?.querySelector('[data-heading-preview]'));
168
+ },
169
+ className: classes.menu,
170
+ },
171
+ headings.map((heading) =>
172
+ h(
173
+ 'button',
174
+ {
175
+ key: heading.id,
176
+ type: 'button',
177
+ ref: (element) => {
178
+ if (current === heading.id) reveal(list.current, element);
179
+ },
180
+ 'aria-current': active === heading.id ? 'location' : undefined,
181
+ 'data-heading-preview': current === heading.id ? '' : undefined,
182
+ className: `${classes.menuButton} ${active === heading.id ? 'text-default' : 'text-secondary'} ${preview === heading.id ? 'bg-background-primary-ghost-hover/50' : ''}`,
183
+ onClick: () => navigate(heading),
184
+ onPointerEnter: () => setPreview(heading.id),
185
+ onFocus: () => setPreview(heading.id),
186
+ },
187
+ h('span', { className: `block ${indentation[Math.min(heading.level, 3)] ?? ''}` }, heading.title),
188
+ ),
189
+ ),
190
+ );
191
+ return h(
192
+ 'nav',
193
+ {
194
+ 'aria-label': '文档目录',
195
+ className: classes.nav,
196
+ style: { top: 'var(--mcx-reading-toc-center, 50%)' },
197
+ },
198
+ h(
199
+ Tooltip,
200
+ {
201
+ align: 'center',
202
+ delayOpen: true,
203
+ interactive: true,
204
+ side: 'right',
205
+ sideOffset: 0,
206
+ skipDelayKey: 'page-table-of-contents',
207
+ tooltipMaxWidth: 'min(20rem, calc(100vw - 16px))',
208
+ variant: 'floating-navigation-rail',
209
+ onPointerLeave: () => {
210
+ if (!list.current) setPreview(null);
211
+ },
212
+ onOpenChange: (open) => {
213
+ if (!open) setPreview(null);
214
+ },
215
+ tooltipContent: listTree,
216
+ },
217
+ railTree,
218
+ ),
219
+ );
220
+ }
221
+ function render() {
222
+ let tree = visible ? h(Contents, { headings, active }) : null;
223
+ root.render(withProviders(React, tree));
224
+ }
225
+ function build() {
226
+ const next = [],
227
+ text = view.state.doc.toString();
228
+ const frontmatter = /^---\r?\n[\s\S]*?\r?\n(?:---|\.\.\.)(?:\r?\n|$)/.exec(text);
229
+ view.state.tree.iterate({
230
+ enter(node) {
231
+ const match = /^(?:ATXHeading|SetextHeading)([1-6])$/.exec(node.name);
232
+ if (
233
+ !match ||
234
+ (frontmatter && node.from < frontmatter[0].length) ||
235
+ node.node.parent?.name !== 'Document'
236
+ )
237
+ return;
238
+ const omit = [];
239
+ node.node.toTree().iterate({
240
+ enter(child) {
241
+ if (
242
+ /^(HeaderMark|EmphasisMark|StrikethroughMark|CodeMark|LinkMark|URL|LinkTitle)$/.test(child.name)
243
+ )
244
+ omit.push([node.from + child.from, node.from + child.to]);
245
+ },
246
+ });
247
+ let title = '',
248
+ cursor = node.from;
249
+ for (const [from, to] of omit.sort((a, b) => a[0] - b[0]))
250
+ if (from >= cursor) {
251
+ title += view.state.sliceDoc(cursor, from);
252
+ cursor = to;
253
+ }
254
+ title = (title + view.state.sliceDoc(cursor, node.to)).replace(/\s+/g, ' ').trim();
255
+ if (title) next.push({ id: `heading-${node.from}`, from: node.from, level: Number(match[1]), title });
256
+ return false;
257
+ },
258
+ });
259
+ if (JSON.stringify(next) !== JSON.stringify(headings)) {
260
+ headings = next;
261
+ render();
262
+ }
263
+ }
264
+ function update() {
265
+ frame = null;
266
+ const scroller = view.scrollDOM,
267
+ rect = scroller.getBoundingClientRect(),
268
+ scale = rect.width / scroller.offsetWidth || 1;
269
+ // 以整个 File Panel(含 Toolbar)的中心锚定,换算回编辑器局部坐标。
270
+ if (panel) {
271
+ const panelRect = panel.getBoundingClientRect(),
272
+ editorRect = view.dom.getBoundingClientRect();
273
+ const editorScale = editorRect.height / view.dom.offsetHeight || 1;
274
+ mount.style.setProperty(
275
+ '--mcx-reading-toc-center',
276
+ `${(panelRect.top + panelRect.height / 2 - editorRect.top) / editorScale}px`,
277
+ );
278
+ }
279
+ const nextVisible =
280
+ headings.length >= 2 && (view.contentDOM.getBoundingClientRect().left - rect.left) / scale >= 112;
281
+ if (!navigating && headings.length) {
282
+ const position = view.lineBlockAtHeight(Math.max(0, rect.top + 24 * scale - view.documentTop)).from;
283
+ active = headings[0].id;
284
+ for (const heading of headings) {
285
+ if (heading.from > position) break;
286
+ active = heading.id;
287
+ }
288
+ if (scroller.scrollTop > 0 && scroller.scrollHeight - scroller.clientHeight - scroller.scrollTop <= 1)
289
+ active = headings.at(-1).id;
290
+ }
291
+ visible = nextVisible;
292
+ render();
293
+ }
294
+ function schedule() {
295
+ if (frame === null) frame = requestAnimationFrame(update);
296
+ }
297
+ view.scrollDOM.addEventListener('scroll', schedule, { signal, passive: true });
298
+ for (const type of ['scrollend', 'wheel', 'touchstart', 'pointerdown', 'keydown'])
299
+ view.scrollDOM.addEventListener(
300
+ type,
301
+ () => {
302
+ navigating = false;
303
+ restoreScroll();
304
+ schedule();
305
+ },
306
+ { signal, passive: true },
307
+ );
308
+ const resize = new ResizeObserver(schedule);
309
+ resize.observe(view.dom);
310
+ resize.observe(view.contentDOM);
311
+ if (panel) resize.observe(panel);
312
+ const plugin = api.ViewPlugin.fromClass(
313
+ class {
314
+ update(update) {
315
+ if (update.docChanged || update.viewportChanged || update.startState.tree !== update.state.tree) {
316
+ build();
317
+ schedule();
318
+ }
319
+ }
320
+ },
321
+ );
322
+ view.dispatch({ effects: api.StateEffect.appendConfig.of(compartment.of(plugin)) });
323
+ build();
324
+ schedule();
325
+ return () => {
326
+ controller.abort();
327
+ resize.disconnect();
328
+ restoreScroll();
329
+ if (frame !== null) cancelAnimationFrame(frame);
330
+ root.unmount();
331
+ mount.remove();
332
+ if (!view.destroyed) view.dispatch({ effects: compartment.reconfigure([]) });
333
+ };
334
+ }
@@ -0,0 +1,56 @@
1
+ // 输入只有正文与排除区间;编号规则不依赖 Codex 语法树、DOM 或编辑器。
2
+ export function collectMarkdownFootnotes(text, excludedRanges) {
3
+ let offset = 0;
4
+ const lines = text.split('\n').map((value) => {
5
+ const line = { text: value, from: offset, to: offset + value.length };
6
+ offset = line.to + 1;
7
+ return line;
8
+ });
9
+ const excluded = (position) => excludedRanges.some(([from, to]) => position >= from && position < to);
10
+ const definitions = new Map();
11
+ for (let index = 0; index < lines.length; index++) {
12
+ const line = lines[index];
13
+ const match = line.text.match(/^ {0,3}\[\^([^\]]+)\]:\s*(.*)$/);
14
+ if (!match || excluded(line.from)) continue;
15
+ const body = [match[2]];
16
+ let end = index;
17
+ while (end + 1 < lines.length && /^(?: {4}|\t)/.test(lines[end + 1].text)) {
18
+ end++;
19
+ body.push(lines[end].text.replace(/^(?: {4}|\t)/, ''));
20
+ }
21
+ definitions.set(match[1], {
22
+ from: line.from,
23
+ to: lines[end].to,
24
+ body: body.join('\n'),
25
+ refs: [],
26
+ number: 0,
27
+ });
28
+ index = end;
29
+ }
30
+ const notes = [...definitions.values()];
31
+ const references = [];
32
+ let number = 0;
33
+ for (const match of text.matchAll(/\[\^([^\]\n]+)\]/g)) {
34
+ const escaped = (text.slice(0, match.index).match(/\\+$/)?.[0].length || 0) % 2;
35
+ const note = definitions.get(match[1]);
36
+ if (
37
+ escaped ||
38
+ !note ||
39
+ excluded(match.index) ||
40
+ notes.some((note) => match.index >= note.from && match.index < note.to)
41
+ )
42
+ continue;
43
+ if (!note.number) note.number = ++number;
44
+ note.refs.push(match.index);
45
+ references.push({
46
+ id: match[1],
47
+ from: match.index,
48
+ to: match.index + match[0].length,
49
+ number: note.number,
50
+ definitionFrom: note.from,
51
+ });
52
+ }
53
+ // 没有引用的定义仍保留,沿用正文末尾的连续编号。
54
+ for (const note of notes) if (!note.number) note.number = ++number;
55
+ return { definitions, references };
56
+ }
@@ -0,0 +1,43 @@
1
+ import { loadNativeMarkdownRenderer } from './adapters/codex/markdown-native.mjs';
2
+ import { collectMarkdownFootnotes } from './domain/footnotes.mjs';
3
+ import { captureMarkdownProviders } from './adapters/codex/providers.mjs';
4
+ import {
5
+ createMarkdownReadingRuntime,
6
+ installMarkdownMetadataScroll,
7
+ } from './adapters/codex/reading-runtime.mjs';
8
+ import { loadNativeMarkdownRichText, installMarkdownRichText } from './adapters/codex/rich-text.mjs';
9
+ import {
10
+ loadNativeMarkdownTableActions,
11
+ installMarkdownTableActions,
12
+ } from './adapters/codex/table-actions.mjs';
13
+ import { loadNativeMarkdownLinks, installMarkdownLinks } from './adapters/codex/links.mjs';
14
+ import { loadNativeMarkdownToc } from './adapters/codex/toc-native.mjs';
15
+ import { installMarkdownToc } from './adapters/codex/toc.mjs';
16
+ import { readFile } from 'node:fs/promises';
17
+ import { installMarkdownTables } from './adapters/codex/tables.mjs';
18
+ import { loadMarkdownListAPI, installMarkdownLists } from './adapters/codex/lists.mjs';
19
+ import { createMarkdownPreviewAdapter } from './adapters/codex/preview.mjs';
20
+ export async function createPageScriptSource() {
21
+ const bindings = [
22
+ collectMarkdownFootnotes,
23
+ captureMarkdownProviders,
24
+ createMarkdownReadingRuntime,
25
+ createMarkdownPreviewAdapter,
26
+ installMarkdownMetadataScroll,
27
+ loadMarkdownListAPI,
28
+ installMarkdownLists,
29
+ loadNativeMarkdownLinks,
30
+ installMarkdownLinks,
31
+ installMarkdownTables,
32
+ loadNativeMarkdownToc,
33
+ installMarkdownToc,
34
+ loadNativeMarkdownRenderer,
35
+ loadNativeMarkdownTableActions,
36
+ installMarkdownTableActions,
37
+ loadNativeMarkdownRichText,
38
+ installMarkdownRichText,
39
+ ];
40
+ const declarations = bindings.map((binding) => `const ${binding.name} = ${binding.toString()};`).join('\n');
41
+ const entry = await readFile(new URL('./markdown-reading.mjs', import.meta.url), 'utf8');
42
+ return `${declarations}\n${entry}`;
43
+ }
@@ -0,0 +1,82 @@
1
+ const marker = 'data-minecodex-markdown-reading';
2
+ const native = createMarkdownPreviewAdapter(config.codex);
3
+ const styled = new Map();
4
+ const sessions = new Map();
5
+ const tokenNames = ['--font-content', '--text-document', '--leading-relaxed', '--font-weight-semibold'];
6
+ const style = document.createElement('style');
7
+ style.setAttribute('data-minecodex-markdown-reading-styles', '');
8
+ style.textContent = config.resources.styles;
9
+ document.head.append(style);
10
+ let disposed = false;
11
+ let pending = null;
12
+
13
+ function release(editor) {
14
+ const binding = styled.get(editor);
15
+ sessions.get(editor)?.dispose();
16
+ sessions.delete(editor);
17
+ editor.removeAttribute(marker);
18
+ binding?.();
19
+ styled.delete(editor);
20
+ }
21
+ function reconcile() {
22
+ pending = null;
23
+ if (disposed) return;
24
+ const previews = native.previews();
25
+ const editors = new Set(previews.map((item) => item.editor));
26
+ for (const editor of styled.keys()) if (!editors.has(editor)) release(editor);
27
+ for (const preview of previews) {
28
+ const { editor } = preview;
29
+ if (!styled.has(editor)) styled.set(editor, config.ui.theme.bindScope(editor, tokenNames));
30
+ if (!editor.hasAttribute(marker)) editor.setAttribute(marker, '');
31
+ if (!sessions.has(editor)) sessions.set(editor, native.attach(preview));
32
+ }
33
+ }
34
+
35
+ function schedule(records) {
36
+ // 忽略原生选区与逐行装饰更新;只在预览视图或其祖先变化时重新定位。
37
+ if (records.every((record) => record.target.closest?.('.cm-content, [data-mcx-reading-toc]'))) return;
38
+ if (!disposed && pending === null) pending = window.requestAnimationFrame(reconcile);
39
+ }
40
+ const observer = new MutationObserver(schedule);
41
+ observer.observe(document.body, { childList: true, subtree: true });
42
+ reconcile();
43
+ config.registerSelfCheck?.(() => {
44
+ const previews = native.previews();
45
+ const styleAvailable =
46
+ style.isConnected && previews.length > 0 && previews.every(({ editor }) => editor.hasAttribute(marker));
47
+ const sessionChecks = previews.map(({ editor }) => sessions.get(editor)?.checks() ?? []);
48
+ const checks = previews.length
49
+ ? [
50
+ { name: 'markdown-preview-styled', available: styleAvailable },
51
+ ...(sessionChecks[0] ?? []).map((check) => ({
52
+ name: check.name,
53
+ available: sessionChecks.every((items) =>
54
+ items.some((item) => item.name === check.name && item.available),
55
+ ),
56
+ })),
57
+ ]
58
+ : [];
59
+ const available =
60
+ styleAvailable &&
61
+ sessionChecks.every((items) => items.length > 0) &&
62
+ checks.every((check) => check.available);
63
+ return {
64
+ state: previews.length ? (available ? 'available' : 'unavailable') : 'not-applicable',
65
+ reason: previews.length
66
+ ? available
67
+ ? null
68
+ : styleAvailable
69
+ ? 'markdown-reading-layout-unavailable'
70
+ : 'markdown-style-unavailable'
71
+ : 'no-markdown-preview',
72
+ checks,
73
+ scopes: previews.map(({ editor }) => ({ name: 'markdown-preview', element: editor })),
74
+ };
75
+ });
76
+ return () => {
77
+ disposed = true;
78
+ observer.disconnect();
79
+ if (pending !== null) window.cancelAnimationFrame(pending);
80
+ for (const editor of styled.keys()) release(editor);
81
+ style.remove();
82
+ };