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,168 @@
1
+ // 使用当前编辑器所属的原生 CodeMirror 实例,避免引入第二套编辑器依赖。
2
+ export async function loadMarkdownListAPI(view) {
3
+ const links = [...document.querySelectorAll('link[rel="modulepreload"]')];
4
+ for (const link of links) {
5
+ if (!/\/dist-[\w-]+\.js$/.test(link.href)) continue;
6
+ const exports = Object.values(await import(link.href));
7
+ if (!exports.includes(view.constructor)) continue;
8
+ const Decoration = exports.find((value) => value?.line && value?.replace && value?.mark);
9
+ const ViewPlugin = exports.find((value) => value?.fromClass && value?.define);
10
+ const StateEffect = exports.find((value) => value?.appendConfig && value?.reconfigure);
11
+ const Compartment = exports.find(
12
+ (value) => value?.prototype?.reconfigure && value?.prototype?.of && value?.prototype?.get,
13
+ );
14
+ const WidgetType = exports.find(
15
+ (value) => value?.prototype?.eq && value?.prototype?.ignoreEvent && value?.prototype?.updateDOM,
16
+ );
17
+ const StateField = exports.find(
18
+ (value) =>
19
+ value?.define && value?.prototype?.slot && value?.prototype?.init && value?.prototype?.create,
20
+ );
21
+ const Prec = exports.find((value) => value?.highest && value?.lowest && value?.high);
22
+ if (Decoration && ViewPlugin && StateEffect && Compartment && WidgetType && Prec && StateField)
23
+ return { Decoration, ViewPlugin, StateEffect, Compartment, WidgetType, Prec, StateField };
24
+ }
25
+ throw new Error('native-markdown-list-api-unavailable');
26
+ }
27
+ export function installMarkdownLists(view, api) {
28
+ const { Decoration, ViewPlugin, StateEffect, Compartment, WidgetType, Prec } = api;
29
+ class Marker extends WidgetType {
30
+ constructor(kind, label) {
31
+ super();
32
+ this.kind = kind;
33
+ this.label = label;
34
+ }
35
+ eq(other) {
36
+ return this.kind === other.kind && this.label === other.label;
37
+ }
38
+ toDOM() {
39
+ const span = document.createElement('span');
40
+ span.className = `mcx-reading-list-marker mcx-reading-${this.kind}`;
41
+ if (this.kind.startsWith('task')) {
42
+ span.setAttribute('role', 'img');
43
+ span.setAttribute('aria-label', this.kind === 'task-checked' ? '已完成' : '未完成');
44
+ span.append(document.createElement('span'));
45
+ } else {
46
+ span.textContent = this.label;
47
+ span.setAttribute('aria-hidden', 'true');
48
+ }
49
+ return span;
50
+ }
51
+ ignoreEvent() {
52
+ return false;
53
+ }
54
+ }
55
+ function build(editor) {
56
+ const tree = editor.state.tree;
57
+ if (!tree) return Decoration.none;
58
+ const rows = new Map();
59
+ tree.iterate({
60
+ from: editor.viewport.from,
61
+ to: editor.viewport.to,
62
+ enter(node) {
63
+ if (node.name !== 'ListItem') return;
64
+ const item = node.node;
65
+ const mark = item.getChild('ListMark');
66
+ if (!mark) return;
67
+ let depth = 0;
68
+ for (let parent = item.parent; parent; parent = parent.parent)
69
+ if (parent.name === 'ListItem') depth++;
70
+ const first = editor.state.doc.lineAt(item.from);
71
+ const last = editor.state.doc.lineAt(item.to);
72
+ const task = item.getChild('Task')?.getChild('TaskMarker');
73
+ let end = task?.to ?? mark.to;
74
+ while (/[\t ]/.test(editor.state.sliceDoc(end, end + 1))) end++;
75
+ const prefix = editor.state.sliceDoc(first.from, mark.from);
76
+ if (!/^[\t >]*$/.test(prefix)) return;
77
+ const quoteDepth = (prefix.match(/>/g) || []).length;
78
+ const raw = editor.state.sliceDoc(mark.from, mark.to);
79
+ const kind = task
80
+ ? /x/i.test(editor.state.sliceDoc(task.from, task.to))
81
+ ? 'task-checked'
82
+ : 'task-empty'
83
+ : item.parent.name === 'OrderedList'
84
+ ? 'ordered'
85
+ : ['disc', 'circle', 'square'][Math.min(depth, 2)];
86
+ for (let number = first.number; number <= last.number; number++) {
87
+ const line = editor.state.doc.line(number);
88
+ if (line.to < editor.viewport.from || line.from > editor.viewport.to) continue;
89
+ let syntax = tree.resolveInner(line.from + (line.text.match(/^[\t ]*/)?.[0].length ?? 0), 1);
90
+ let block = false;
91
+ for (; syntax; syntax = syntax.parent)
92
+ if (['CodeBlock', 'FencedCode', 'Table', 'MathBlock', 'HTMLBlock'].includes(syntax.name))
93
+ block = true;
94
+ if (block) continue;
95
+ const current = rows.get(line.from);
96
+ if (!current || current.depth <= depth)
97
+ rows.set(line.from, {
98
+ line,
99
+ depth,
100
+ quoteDepth,
101
+ kind,
102
+ raw,
103
+ end,
104
+ first: number === first.number,
105
+ done: kind === 'task-checked' && line.from < (item.getChild('Task')?.to ?? 0),
106
+ trim: mark.to - first.from + 1,
107
+ });
108
+ }
109
+ },
110
+ });
111
+ const ranges = [];
112
+ for (const row of rows.values()) {
113
+ const { line, depth, kind, raw, end, first, done, trim, quoteDepth } = row;
114
+ // 光标进入标记时显示源码,编辑和选区继续由原生负责。
115
+ if (
116
+ first &&
117
+ view.hasFocus &&
118
+ view.state.selection.ranges.some((range) => range.from <= end && range.to >= line.from)
119
+ )
120
+ continue;
121
+ const markerWidth = kind === 'ordered' ? Math.max(1.75, raw.length * 0.65 + 0.5) : 1.75;
122
+ ranges.push(
123
+ Decoration.line({
124
+ class: `mcx-reading-list-row${first ? ' mcx-reading-list-first' : ''}${done ? ' mcx-reading-task-done' : ''}`,
125
+ attributes: {
126
+ style: `--mcx-list-indent:calc(${depth * 1.75}em + ${quoteDepth * 20}px);--mcx-list-marker-width:${markerWidth}em`,
127
+ },
128
+ }).range(line.from),
129
+ );
130
+ if (first)
131
+ ranges.push(
132
+ Decoration.replace({ widget: new Marker(kind, kind === 'ordered' ? raw : '') }).range(
133
+ line.from,
134
+ end,
135
+ ),
136
+ );
137
+ else {
138
+ const spaces = line.text.match(/^[\t ]+/)?.[0];
139
+ if (spaces)
140
+ ranges.push(Decoration.replace({}).range(line.from, line.from + Math.min(spaces.length, trim)));
141
+ }
142
+ }
143
+ return Decoration.set(ranges, true);
144
+ }
145
+ const plugin = ViewPlugin.define(
146
+ (editor) => ({
147
+ decorations: build(editor),
148
+ update(update) {
149
+ if (
150
+ update.docChanged ||
151
+ update.viewportChanged ||
152
+ update.selectionSet ||
153
+ update.focusChanged ||
154
+ update.startState.tree !== update.state.tree
155
+ )
156
+ this.decorations = build(update.view);
157
+ },
158
+ }),
159
+ { decorations: (value) => value.decorations },
160
+ );
161
+ const compartment = new Compartment();
162
+ view.dispatch({
163
+ effects: StateEffect.appendConfig.of(compartment.of(Prec ? Prec.highest(plugin) : plugin)),
164
+ });
165
+ return () => {
166
+ if (!view.destroyed) view.dispatch({ effects: compartment.reconfigure([]) });
167
+ };
168
+ }
@@ -0,0 +1,28 @@
1
+ // 消息流 Markdown 解析与展示组件的原生契约,供表格预览和富文本共用。
2
+ export async function loadNativeMarkdownRenderer(source, url, module) {
3
+ const exported = source.slice(source.lastIndexOf('export{'));
4
+ const binding = (name) =>
5
+ module[exported.match(new RegExp(`\\b${name.replace(/\$/g, '\\$')} as ([\\w$]+)`))?.[1]];
6
+ const at = source.indexOf('getDefaultComponents:r,...n');
7
+ const wrapper = source.slice(source.lastIndexOf('function ', at), source.indexOf('function ', at));
8
+ const markdownName = wrapper.match(/^function ([\w$]+)/)?.[1];
9
+ const after = source.slice(source.indexOf(wrapper) + wrapper.length);
10
+ const init = after.match(/^function ([\w$]+)\(\)/)?.[1];
11
+ const initializer = [
12
+ ...source.matchAll(/function ([\w$]+)\(\)\{return\(\1=[\w$]+\(\(\(\)=>\{([^}]{0,1600})\}\)\)\)\(\)\}/g),
13
+ ].find((m) => m[2].includes(`${init}()`) && binding(m[1]));
14
+ if (!initializer || !binding(markdownName)) throw new Error('native-rich-markdown-contract-unavailable');
15
+ binding(initializer[1])();
16
+ let lexer, sharedUrl;
17
+ const lexerName = source.match(/tokens:([\w$]+)\(e,void 0\)/)?.[1];
18
+ for (const m of source.matchAll(/import\{([^}]+)\}from["']([^"']+)["']/g))
19
+ for (const entry of m[1].split(',')) {
20
+ const parts = entry.trim().split(/\s+as\s+/);
21
+ if (parts.at(-1) === lexerName) {
22
+ sharedUrl = new URL(m[2], url).href;
23
+ lexer = (await import(sharedUrl))[parts[0]];
24
+ }
25
+ }
26
+ if (!sharedUrl || typeof lexer !== 'function') throw new Error('native-markdown-lexer-unavailable');
27
+ return { Markdown: binding(markdownName), lexer, sharedUrl };
28
+ }
@@ -0,0 +1,38 @@
1
+ import { createMarkdownReadingRuntime } from './reading-runtime.mjs';
2
+
3
+ // 当前原生 Markdown Preview 是 CodeMirror 视图;Source 与 Pages 不属于此契约。
4
+ export function createMarkdownPreviewAdapter(codex) {
5
+ let runtime;
6
+ function preview(editor) {
7
+ if (!editor.isConnected || !editor.closest('[data-editor-search-surface]')) return null;
8
+ const content = editor.querySelector('.cm-content[data-language="markdown"]');
9
+ const scroller = editor.querySelector('.cm-scroller');
10
+ if (!content || !scroller) return null;
11
+ const file = codex.props(
12
+ editor,
13
+ (props) =>
14
+ props.fileKind === 'markdown' && typeof props.filePath === 'string' && 'onSaveChange' in props,
15
+ );
16
+ const mode =
17
+ file &&
18
+ codex.props(
19
+ editor,
20
+ (props) =>
21
+ props.filePath === file.filePath && 'initialMode' in props && 'onSourceDraftChange' in props,
22
+ );
23
+ return mode ? { editor, content, scroller } : null;
24
+ }
25
+ function previews() {
26
+ const items = [...document.querySelectorAll('.cm-editor')].map(preview).filter(Boolean);
27
+ codex.report('markdown-preview', items.length > 0, items.length ? null : 'no-markdown-preview');
28
+ return items;
29
+ }
30
+ return {
31
+ preview,
32
+ previews,
33
+ attach(item) {
34
+ runtime ??= createMarkdownReadingRuntime(codex);
35
+ return runtime.attach(item);
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,18 @@
1
+ // 同一文件视图下的附加 React 根继承最近的 Provider;原始 Fiber 不离开适配层。
2
+ export function captureMarkdownProviders(element, codex) {
3
+ const providers = [],
4
+ seen = new Set();
5
+ let fiber;
6
+ for (let anchor = element; anchor && !fiber; anchor = anchor.parentElement)
7
+ fiber = codex.currentFiber(anchor);
8
+ for (; fiber; fiber = fiber.return) {
9
+ if (fiber.tag !== 10 || seen.has(fiber.type)) continue;
10
+ providers.push({ type: fiber.type, value: fiber.memoizedProps.value });
11
+ seen.add(fiber.type);
12
+ }
13
+ return (React, tree) => {
14
+ for (const provider of providers)
15
+ tree = React.createElement(provider.type, { value: provider.value }, tree);
16
+ return tree;
17
+ };
18
+ }
@@ -0,0 +1,152 @@
1
+ import { loadMarkdownListAPI, installMarkdownLists } from './lists.mjs';
2
+ import { loadNativeMarkdownLinks, installMarkdownLinks } from './links.mjs';
3
+ import { installMarkdownTables } from './tables.mjs';
4
+ import { loadNativeMarkdownToc } from './toc-native.mjs';
5
+ import { installMarkdownToc } from './toc.mjs';
6
+ import { loadNativeMarkdownTableActions, installMarkdownTableActions } from './table-actions.mjs';
7
+ import { loadNativeMarkdownRichText, installMarkdownRichText } from './rich-text.mjs';
8
+
9
+ // 原生能力加载与会话寿命归 Adapter;入口只持有检查结果和释放动作。
10
+ export function createMarkdownReadingRuntime(
11
+ codex,
12
+ capabilities = {
13
+ loadMarkdownListAPI,
14
+ installMarkdownLists,
15
+ loadNativeMarkdownLinks,
16
+ installMarkdownLinks,
17
+ installMarkdownTables,
18
+ loadNativeMarkdownToc,
19
+ installMarkdownToc,
20
+ loadNativeMarkdownTableActions,
21
+ installMarkdownTableActions,
22
+ loadNativeMarkdownRichText,
23
+ installMarkdownRichText,
24
+ installMarkdownMetadataScroll,
25
+ },
26
+ ) {
27
+ const cached = new Map();
28
+ function load(name, ...args) {
29
+ if (!cached.has(name)) {
30
+ const pending = Promise.resolve().then(() => capabilities[name](...args));
31
+ cached.set(name, pending);
32
+ pending.catch(() => {
33
+ if (cached.get(name) === pending) cached.delete(name);
34
+ });
35
+ }
36
+ return cached.get(name);
37
+ }
38
+ function attach({ editor, content, scroller }) {
39
+ const view = content.cmTile?.view;
40
+ const states = new Map([
41
+ ['markdown-reading-layout', 'loading'],
42
+ ['markdown-reading-links', 'loading'],
43
+ ['markdown-reading-toc', 'loading'],
44
+ ['markdown-table-actions', 'loading'],
45
+ ['markdown-reading-rich-text', 'loading'],
46
+ ]);
47
+ const cleanups = [capabilities.installMarkdownMetadataScroll(editor, scroller)];
48
+ let disposed = false;
49
+ const current = () => !disposed && !view?.destroyed;
50
+ function report(name, available, reason = null) {
51
+ if (!current()) return;
52
+ states.set(name, available ? 'available' : 'unavailable');
53
+ codex.report(name, available, reason);
54
+ }
55
+ async function stage(name, reason, run) {
56
+ try {
57
+ await run();
58
+ } catch {
59
+ report(name, false, reason);
60
+ }
61
+ }
62
+ async function installLinks(api) {
63
+ await stage('markdown-reading-links', 'native-link-adapter-unavailable', async () => {
64
+ const openHref = await load('loadNativeMarkdownLinks');
65
+ if (!current()) return;
66
+ cleanups.push(capabilities.installMarkdownLinks(view, api, openHref));
67
+ report('markdown-reading-links', true);
68
+ });
69
+ }
70
+ async function installRichText(api, actions) {
71
+ await stage('markdown-reading-rich-text', 'native-rich-text-unavailable', async () => {
72
+ const rich = await load('loadNativeMarkdownRichText', actions);
73
+ if (!current()) return;
74
+ cleanups.push(capabilities.installMarkdownRichText(view, api, rich, codex));
75
+ report('markdown-reading-rich-text', true);
76
+ });
77
+ }
78
+ async function installActions(api, toc, tables) {
79
+ await stage('markdown-table-actions', 'native-table-actions-unavailable', async () => {
80
+ const actions = await load('loadNativeMarkdownTableActions', toc);
81
+ if (!current()) return;
82
+ cleanups.push(capabilities.installMarkdownTableActions(view, actions, codex, tables.controller));
83
+ report('markdown-table-actions', true);
84
+ await installRichText(api, actions);
85
+ });
86
+ }
87
+ async function installToc(api, tables) {
88
+ await stage('markdown-reading-toc', 'native-page-toc-unavailable', async () => {
89
+ const toc = await load('loadNativeMarkdownToc');
90
+ if (!current()) return;
91
+ cleanups.push(capabilities.installMarkdownToc(view, api, toc, codex));
92
+ report('markdown-reading-toc', true);
93
+ await installActions(api, toc, tables);
94
+ });
95
+ }
96
+ async function start() {
97
+ if (!view || view.dom !== editor || !view.state.tree) {
98
+ report('markdown-reading-layout', false, 'native-markdown-view-unavailable');
99
+ return;
100
+ }
101
+ await stage('markdown-reading-layout', 'native-markdown-list-api-unavailable', async () => {
102
+ const api = await capabilities.loadMarkdownListAPI(view);
103
+ if (!current()) return;
104
+ cleanups.push(capabilities.installMarkdownLists(view, api));
105
+ const tables = capabilities.installMarkdownTables(view, api);
106
+ cleanups.push(tables);
107
+ report('markdown-reading-layout', true);
108
+ await Promise.all([installLinks(api), installToc(api, tables)]);
109
+ });
110
+ }
111
+ const ready = start();
112
+ return {
113
+ ready,
114
+ checks: () => [...states].map(([name, state]) => ({ name, available: state === 'available' })),
115
+ dispose() {
116
+ if (disposed) return;
117
+ disposed = true;
118
+ // 先释放依赖方,再移除其所依赖的装饰和滚动控制器。
119
+ for (const cleanup of cleanups.splice(0).reverse()) cleanup();
120
+ },
121
+ };
122
+ }
123
+ return { attach };
124
+ }
125
+
126
+ // 保留 React 的兄弟节点结构,让 Metadata 与原生正文共用一个滚动位置。
127
+ export function installMarkdownMetadataScroll(editor, scroller) {
128
+ const body = editor.parentElement?.parentElement;
129
+ const frame = body?.parentElement;
130
+ const metadata =
131
+ frame && [...frame.children].find((node) => node !== body && node.matches('.max-h-80.overflow-y-auto'));
132
+ if (!metadata) return () => {};
133
+ frame.setAttribute('data-mcx-reading-metadata-frame', '');
134
+ metadata.setAttribute('data-mcx-reading-metadata', '');
135
+ function sync() {
136
+ editor.style.setProperty('--mcx-reading-metadata-height', `${metadata.offsetHeight}px`);
137
+ metadata.style.transform = `translateY(${-scroller.scrollTop}px)`;
138
+ }
139
+ const originalTransform = metadata.style.transform;
140
+ const observer = new ResizeObserver(sync);
141
+ observer.observe(metadata);
142
+ scroller.addEventListener('scroll', sync, { passive: true });
143
+ sync();
144
+ return () => {
145
+ observer.disconnect();
146
+ scroller.removeEventListener('scroll', sync);
147
+ metadata.style.transform = originalTransform;
148
+ metadata.removeAttribute('data-mcx-reading-metadata');
149
+ frame.removeAttribute('data-mcx-reading-metadata-frame');
150
+ editor.style.removeProperty('--mcx-reading-metadata-height');
151
+ };
152
+ }