mrmd-editor 0.6.0 → 0.7.0
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.
- package/package.json +1 -1
- package/src/cell-controls/widgets.js +30 -0
- package/src/cells.js +9 -9
- package/src/ctrl-k-modal.js +190 -14
- package/src/document-languages.js +105 -0
- package/src/execution.js +4 -10
- package/src/frontmatter-updater.js +224 -0
- package/src/index.js +127 -86
- package/src/markdown/renderer.js +52 -3
- package/src/markdown/styles.js +126 -0
- package/src/monitor-coordination.js +1 -3
- package/src/mrp-client.js +36 -169
- package/src/mrp-types.js +1 -37
- package/src/output-widget.js +54 -0
- package/src/runtime-codelens/index.js +3 -3
- package/src/shell/ai-menu.js +70 -0
- package/src/shell/components/menu.js +39 -1
- package/src/shell/components/status-bar.js +5 -5
- package/src/shell/dialogs/file-picker.js +167 -6
- package/src/shell/layouts/studio.js +8 -9
- package/src/shell/orchestrator-client.js +60 -18
- package/src/shell/state.js +63 -42
- package/src/shell/styles.js +266 -0
package/src/index.js
CHANGED
|
@@ -304,55 +304,55 @@ function parseProgress(output) {
|
|
|
304
304
|
function createJavaScriptRuntime(options = {}) {
|
|
305
305
|
const rt = createMrmdJsRuntime(options);
|
|
306
306
|
|
|
307
|
-
// Track named
|
|
308
|
-
//
|
|
309
|
-
// null/undefined/'default'/'main'/'none' →
|
|
307
|
+
// Track named execution contexts - each can have different isolation
|
|
308
|
+
// Context naming:
|
|
309
|
+
// null/undefined/'default'/'main'/'none' → configured default isolation
|
|
310
310
|
// 'sandbox'/'iframe' → sandboxed iframe
|
|
311
311
|
// other names → sandboxed iframe with separate scope
|
|
312
|
-
const
|
|
312
|
+
const contexts = new Map();
|
|
313
313
|
const defaultIsolation = options.defaultIsolation || 'iframe';
|
|
314
314
|
|
|
315
315
|
/**
|
|
316
|
-
* Get or create a
|
|
317
|
-
* @param {string|null}
|
|
316
|
+
* Get or create a context by name
|
|
317
|
+
* @param {string|null} contextName
|
|
318
318
|
* @returns {Session}
|
|
319
319
|
*/
|
|
320
|
-
function
|
|
321
|
-
// Normalize
|
|
322
|
-
const name =
|
|
320
|
+
function getOrCreateContext(contextName) {
|
|
321
|
+
// Normalize context name
|
|
322
|
+
const name = contextName || 'default';
|
|
323
323
|
|
|
324
|
-
// Return existing
|
|
325
|
-
if (
|
|
326
|
-
return
|
|
324
|
+
// Return existing context
|
|
325
|
+
if (contexts.has(name)) {
|
|
326
|
+
return contexts.get(name);
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
// Determine isolation mode based on
|
|
329
|
+
// Determine isolation mode based on context name
|
|
330
330
|
let isolation;
|
|
331
|
-
if (!
|
|
332
|
-
// Default
|
|
331
|
+
if (!contextName || contextName === 'default' || contextName === 'main' || contextName === 'none') {
|
|
332
|
+
// Default context uses the configured default isolation
|
|
333
333
|
isolation = defaultIsolation;
|
|
334
|
-
} else if (
|
|
334
|
+
} else if (contextName === 'sandbox' || contextName === 'iframe') {
|
|
335
335
|
// Explicit sandbox request
|
|
336
336
|
isolation = 'iframe';
|
|
337
337
|
} else {
|
|
338
|
-
// Named
|
|
338
|
+
// Named contexts are sandboxed by default (separate scope)
|
|
339
339
|
isolation = 'iframe';
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
// Create new
|
|
343
|
-
const
|
|
342
|
+
// Create new execution context with appropriate isolation
|
|
343
|
+
const context = rt.createSession({
|
|
344
344
|
language: 'javascript',
|
|
345
345
|
isolation,
|
|
346
346
|
id: name,
|
|
347
347
|
});
|
|
348
348
|
|
|
349
|
-
|
|
350
|
-
console.log(`[JS Runtime] Created
|
|
351
|
-
return
|
|
349
|
+
contexts.set(name, context);
|
|
350
|
+
console.log(`[JS Runtime] Created context '${name}' with isolation: ${isolation}`);
|
|
351
|
+
return context;
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
// Create default
|
|
355
|
-
const
|
|
354
|
+
// Create default context eagerly
|
|
355
|
+
const defaultContext = getOrCreateContext('default');
|
|
356
356
|
|
|
357
357
|
// Languages supported by mrmd-js
|
|
358
358
|
const supportedLanguages = {
|
|
@@ -379,8 +379,9 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
379
379
|
/** Execute code (non-streaming) */
|
|
380
380
|
async execute(code, language, execOptions = {}) {
|
|
381
381
|
const lang = supportedLanguages[language.toLowerCase()] || 'javascript';
|
|
382
|
-
const
|
|
383
|
-
const
|
|
382
|
+
const contextName = execOptions.context ?? execOptions.session;
|
|
383
|
+
const context = getOrCreateContext(contextName);
|
|
384
|
+
const result = await context.execute(code, { language: lang });
|
|
384
385
|
return {
|
|
385
386
|
success: result.success,
|
|
386
387
|
stdout: result.stdout || '',
|
|
@@ -394,8 +395,9 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
394
395
|
/** Execute code with streaming output */
|
|
395
396
|
async executeStreaming(code, language, onChunk, onStdinRequest, execOptions = {}) {
|
|
396
397
|
const lang = supportedLanguages[language.toLowerCase()] || 'javascript';
|
|
397
|
-
const
|
|
398
|
-
const
|
|
398
|
+
const contextName = execOptions.context ?? execOptions.session;
|
|
399
|
+
const context = getOrCreateContext(contextName);
|
|
400
|
+
const result = await context.execute(code, { language: lang });
|
|
399
401
|
|
|
400
402
|
// Handle different output types
|
|
401
403
|
let output = result.stdout || '';
|
|
@@ -434,18 +436,18 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
434
436
|
};
|
|
435
437
|
},
|
|
436
438
|
|
|
437
|
-
/** Reset a
|
|
438
|
-
reset(
|
|
439
|
-
const
|
|
440
|
-
if (
|
|
441
|
-
|
|
439
|
+
/** Reset a context (clear all variables) */
|
|
440
|
+
reset(contextName) {
|
|
441
|
+
const context = contexts.get(contextName || 'default');
|
|
442
|
+
if (context) {
|
|
443
|
+
context.reset();
|
|
442
444
|
}
|
|
443
445
|
},
|
|
444
446
|
|
|
445
|
-
/** Reset all
|
|
447
|
+
/** Reset all contexts */
|
|
446
448
|
resetAll() {
|
|
447
|
-
for (const
|
|
448
|
-
|
|
449
|
+
for (const context of contexts.values()) {
|
|
450
|
+
context.reset();
|
|
449
451
|
}
|
|
450
452
|
},
|
|
451
453
|
|
|
@@ -454,23 +456,32 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
454
456
|
return rt;
|
|
455
457
|
},
|
|
456
458
|
|
|
457
|
-
/** Get a
|
|
458
|
-
|
|
459
|
-
return
|
|
459
|
+
/** Get a context by name (default if not specified) */
|
|
460
|
+
getContext(contextName) {
|
|
461
|
+
return getOrCreateContext(contextName);
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
/** List all context names */
|
|
465
|
+
listContexts() {
|
|
466
|
+
return Array.from(contexts.keys());
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
// Legacy aliases (kept for compatibility inside monorepo)
|
|
470
|
+
getSession(contextName) {
|
|
471
|
+
return getOrCreateContext(contextName);
|
|
460
472
|
},
|
|
461
473
|
|
|
462
|
-
/** List all session names */
|
|
463
474
|
listSessions() {
|
|
464
|
-
return Array.from(
|
|
475
|
+
return Array.from(contexts.keys());
|
|
465
476
|
},
|
|
466
477
|
|
|
467
|
-
/** Destroy the runtime and all
|
|
478
|
+
/** Destroy the runtime and all contexts */
|
|
468
479
|
destroy() {
|
|
469
480
|
rt.destroy();
|
|
470
481
|
},
|
|
471
482
|
|
|
472
483
|
// =========================================================================
|
|
473
|
-
// LSP Features (powered by mrmd-js
|
|
484
|
+
// LSP Features (powered by mrmd-js default context)
|
|
474
485
|
// =========================================================================
|
|
475
486
|
|
|
476
487
|
/**
|
|
@@ -482,7 +493,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
482
493
|
* @returns {{found: boolean, name?: string, type?: string, value?: string, signature?: string}|null}
|
|
483
494
|
*/
|
|
484
495
|
hover(code, cursor) {
|
|
485
|
-
return
|
|
496
|
+
return defaultContext.hover(code, cursor);
|
|
486
497
|
},
|
|
487
498
|
|
|
488
499
|
/**
|
|
@@ -494,7 +505,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
494
505
|
* @returns {{matches: Array, cursorStart: number, cursorEnd: number}}
|
|
495
506
|
*/
|
|
496
507
|
complete(code, cursor) {
|
|
497
|
-
return
|
|
508
|
+
return defaultContext.complete(code, cursor);
|
|
498
509
|
},
|
|
499
510
|
|
|
500
511
|
/**
|
|
@@ -506,18 +517,18 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
506
517
|
* @returns {Object|null}
|
|
507
518
|
*/
|
|
508
519
|
inspect(code, cursor, options = {}) {
|
|
509
|
-
return
|
|
520
|
+
return defaultContext.inspect(code, cursor, options);
|
|
510
521
|
},
|
|
511
522
|
|
|
512
523
|
/**
|
|
513
|
-
* List all variables in a
|
|
524
|
+
* List all variables in a context namespace.
|
|
514
525
|
*
|
|
515
526
|
* @param {Object} [filter]
|
|
516
|
-
* @param {string} [
|
|
527
|
+
* @param {string} [contextName='default']
|
|
517
528
|
* @returns {Array<{name: string, type: string, value: string, expandable?: boolean}>}
|
|
518
529
|
*/
|
|
519
|
-
listVariables(filter = {},
|
|
520
|
-
return
|
|
530
|
+
listVariables(filter = {}, contextName = 'default') {
|
|
531
|
+
return getOrCreateContext(contextName).listVariables(filter);
|
|
521
532
|
},
|
|
522
533
|
|
|
523
534
|
/**
|
|
@@ -528,7 +539,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
528
539
|
* @returns {Object}
|
|
529
540
|
*/
|
|
530
541
|
getVariable(name, options = {}) {
|
|
531
|
-
return
|
|
542
|
+
return defaultContext.getVariable(name, options);
|
|
532
543
|
},
|
|
533
544
|
|
|
534
545
|
/**
|
|
@@ -538,7 +549,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
538
549
|
* @returns {{status: 'complete'|'incomplete'|'invalid'|'unknown', indent?: string}}
|
|
539
550
|
*/
|
|
540
551
|
isComplete(code) {
|
|
541
|
-
return
|
|
552
|
+
return defaultContext.isComplete(code);
|
|
542
553
|
},
|
|
543
554
|
|
|
544
555
|
/**
|
|
@@ -548,7 +559,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
548
559
|
* @returns {Promise<{formatted: string, changed: boolean}>}
|
|
549
560
|
*/
|
|
550
561
|
format(code) {
|
|
551
|
-
return
|
|
562
|
+
return defaultContext.format(code);
|
|
552
563
|
},
|
|
553
564
|
|
|
554
565
|
/**
|
|
@@ -556,7 +567,7 @@ function createJavaScriptRuntime(options = {}) {
|
|
|
556
567
|
* @returns {import('./runtime-lsp.js').RuntimeLSPProvider}
|
|
557
568
|
*/
|
|
558
569
|
getLSPProvider() {
|
|
559
|
-
return adaptMrmdJsSession(
|
|
570
|
+
return adaptMrmdJsSession(defaultContext);
|
|
560
571
|
},
|
|
561
572
|
};
|
|
562
573
|
}
|
|
@@ -731,6 +742,15 @@ const codeBlockStyles = EditorView.theme({
|
|
|
731
742
|
'.cm-codeblock-fence::selection, .cm-codeblock-fence *::selection': {
|
|
732
743
|
backgroundColor: 'var(--editor-selection, #264f78) !important',
|
|
733
744
|
},
|
|
745
|
+
// Mobile: code blocks need to be larger and scroll horizontally
|
|
746
|
+
'@media (max-width: 768px)': {
|
|
747
|
+
'.cm-codeblock-line': {
|
|
748
|
+
fontSize: 'max(var(--code-font-size, 0.8em), 13px)',
|
|
749
|
+
},
|
|
750
|
+
'.cm-codeblock-fence': {
|
|
751
|
+
fontSize: '0.6em', // Slightly larger than desktop's 0.5em for visibility
|
|
752
|
+
},
|
|
753
|
+
},
|
|
734
754
|
});
|
|
735
755
|
// #endregion CODE_BLOCK_BACKGROUND
|
|
736
756
|
|
|
@@ -896,6 +916,11 @@ function create(target, options = {}) {
|
|
|
896
916
|
'.cm-gutters': { display: 'none' },
|
|
897
917
|
'.cm-activeLineGutter': { backgroundColor: 'transparent' },
|
|
898
918
|
'&.cm-focused': { outline: 'none' },
|
|
919
|
+
// Mobile: slightly larger text, comfortable line-height
|
|
920
|
+
'@media (max-width: 768px)': {
|
|
921
|
+
'&': { fontSize: '17px' },
|
|
922
|
+
'.cm-scroller': { lineHeight: '1.7' },
|
|
923
|
+
},
|
|
899
924
|
});
|
|
900
925
|
|
|
901
926
|
// Inject CSS styles
|
|
@@ -2046,17 +2071,16 @@ function create(target, options = {}) {
|
|
|
2046
2071
|
* Refresh variables from all MRP runtimes
|
|
2047
2072
|
* Fetches current variable state and updates state.variables
|
|
2048
2073
|
*
|
|
2049
|
-
* @param {string} [sessionId] - Specific session to refresh (optional)
|
|
2050
2074
|
* @returns {Promise<void>}
|
|
2051
2075
|
*/
|
|
2052
|
-
async refreshVariables(
|
|
2076
|
+
async refreshVariables() {
|
|
2053
2077
|
for (const [name, runtime] of registry.runtimes) {
|
|
2054
2078
|
// Check if runtime is an MRP client (has getVariables method)
|
|
2055
2079
|
if (typeof runtime.getVariables === 'function') {
|
|
2056
2080
|
try {
|
|
2057
|
-
const result = await runtime.getVariables(
|
|
2081
|
+
const result = await runtime.getVariables();
|
|
2058
2082
|
if (result && result.variables) {
|
|
2059
|
-
const session =
|
|
2083
|
+
const session = 'default';
|
|
2060
2084
|
const variables = {};
|
|
2061
2085
|
for (const v of result.variables) {
|
|
2062
2086
|
variables[v.name] = {
|
|
@@ -2546,12 +2570,12 @@ function create(target, options = {}) {
|
|
|
2546
2570
|
}
|
|
2547
2571
|
// #endregion CREATE
|
|
2548
2572
|
|
|
2549
|
-
// #region
|
|
2573
|
+
// #region RUNTIME
|
|
2550
2574
|
/**
|
|
2551
|
-
* Create an editor
|
|
2575
|
+
* Create an editor runtime attachment via orchestrator.
|
|
2552
2576
|
*
|
|
2553
2577
|
* This is the simplest way to use mrmd with full features:
|
|
2554
|
-
* - Automatically creates
|
|
2578
|
+
* - Automatically creates/attaches runtime with orchestrator
|
|
2555
2579
|
* - Connects to sync server
|
|
2556
2580
|
* - Sets up Python runtime (shared or dedicated)
|
|
2557
2581
|
* - Enables monitor mode for long-running executions
|
|
@@ -2562,28 +2586,28 @@ function create(target, options = {}) {
|
|
|
2562
2586
|
* @param {string} options.doc - Document name (required)
|
|
2563
2587
|
* @param {string} [options.python='shared'] - 'shared' or 'dedicated'
|
|
2564
2588
|
* @param {Object} [options.editor] - Additional editor options
|
|
2565
|
-
* @returns {Promise<Object>} Editor instance with
|
|
2589
|
+
* @returns {Promise<Object>} Editor instance with destroyRuntime() method
|
|
2566
2590
|
*
|
|
2567
2591
|
* @example
|
|
2568
2592
|
* // Basic usage
|
|
2569
|
-
* const editor = await mrmd.
|
|
2593
|
+
* const editor = await mrmd.runtime('http://localhost:8080', '#editor', {
|
|
2570
2594
|
* doc: 'my-notebook',
|
|
2571
2595
|
* });
|
|
2572
2596
|
*
|
|
2573
2597
|
* // With dedicated Python runtime
|
|
2574
|
-
* const editor = await mrmd.
|
|
2598
|
+
* const editor = await mrmd.runtime('http://localhost:8080', '#editor', {
|
|
2575
2599
|
* doc: 'my-notebook',
|
|
2576
2600
|
* python: 'dedicated',
|
|
2577
2601
|
* });
|
|
2578
2602
|
*
|
|
2579
2603
|
* // Clean up when done
|
|
2580
|
-
* await editor.
|
|
2604
|
+
* await editor.destroyRuntime();
|
|
2581
2605
|
*/
|
|
2582
|
-
async function
|
|
2606
|
+
async function runtime(orchestratorUrl, target, options = {}) {
|
|
2583
2607
|
const { doc, python = 'shared', editor: editorOptions = {} } = options;
|
|
2584
2608
|
|
|
2585
2609
|
if (!doc) {
|
|
2586
|
-
throw new Error('mrmd.
|
|
2610
|
+
throw new Error('mrmd.runtime: doc option is required');
|
|
2587
2611
|
}
|
|
2588
2612
|
|
|
2589
2613
|
// Normalize orchestrator URL
|
|
@@ -2592,24 +2616,33 @@ async function session(orchestratorUrl, target, options = {}) {
|
|
|
2592
2616
|
baseUrl = baseUrl.slice(0, -1);
|
|
2593
2617
|
}
|
|
2594
2618
|
|
|
2595
|
-
// Create
|
|
2596
|
-
console.log(`[mrmd.
|
|
2619
|
+
// Create runtime attachment with orchestrator
|
|
2620
|
+
console.log(`[mrmd.runtime] Creating runtime for '${doc}' (python=${python})`);
|
|
2597
2621
|
|
|
2598
|
-
|
|
2622
|
+
// Prefer /api/runtimes, fall back to /api/sessions for legacy orchestrators
|
|
2623
|
+
let response = await fetch(`${baseUrl}/api/runtimes`, {
|
|
2599
2624
|
method: 'POST',
|
|
2600
2625
|
headers: { 'Content-Type': 'application/json' },
|
|
2601
2626
|
body: JSON.stringify({ doc, python }),
|
|
2602
2627
|
});
|
|
2603
2628
|
|
|
2629
|
+
if (!response.ok && response.status === 404) {
|
|
2630
|
+
response = await fetch(`${baseUrl}/api/sessions`, {
|
|
2631
|
+
method: 'POST',
|
|
2632
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2633
|
+
body: JSON.stringify({ doc, python }),
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2604
2637
|
if (!response.ok) {
|
|
2605
2638
|
const error = await response.text();
|
|
2606
|
-
throw new Error(`Failed to create
|
|
2639
|
+
throw new Error(`Failed to create runtime attachment: ${error}`);
|
|
2607
2640
|
}
|
|
2608
2641
|
|
|
2609
2642
|
const sessionInfo = await response.json();
|
|
2610
|
-
console.log('[mrmd.
|
|
2643
|
+
console.log('[mrmd.runtime] Runtime created:', sessionInfo);
|
|
2611
2644
|
|
|
2612
|
-
// Extract URLs from
|
|
2645
|
+
// Extract URLs from response
|
|
2613
2646
|
const syncUrl = sessionInfo.sync;
|
|
2614
2647
|
const runtimeUrl = sessionInfo.runtimes?.python?.url;
|
|
2615
2648
|
|
|
@@ -2634,17 +2667,16 @@ async function session(orchestratorUrl, target, options = {}) {
|
|
|
2634
2667
|
ydoc: editor.ydoc,
|
|
2635
2668
|
runtimeUrl,
|
|
2636
2669
|
awareness: editor.awareness,
|
|
2637
|
-
session: doc,
|
|
2638
2670
|
});
|
|
2639
2671
|
}
|
|
2640
2672
|
|
|
2641
|
-
// Store
|
|
2673
|
+
// Store runtime info on editor
|
|
2642
2674
|
editor._sessionInfo = sessionInfo;
|
|
2643
2675
|
editor._orchestratorUrl = baseUrl;
|
|
2644
2676
|
|
|
2645
|
-
// Add
|
|
2646
|
-
editor.
|
|
2647
|
-
console.log(`[mrmd.
|
|
2677
|
+
// Add destroyRuntime method
|
|
2678
|
+
editor.destroyRuntime = async function() {
|
|
2679
|
+
console.log(`[mrmd.runtime] Destroying runtime for '${doc}'`);
|
|
2648
2680
|
|
|
2649
2681
|
// Disconnect from sync
|
|
2650
2682
|
if (editor.disconnect) {
|
|
@@ -2653,28 +2685,33 @@ async function session(orchestratorUrl, target, options = {}) {
|
|
|
2653
2685
|
|
|
2654
2686
|
// Call orchestrator to clean up
|
|
2655
2687
|
try {
|
|
2656
|
-
|
|
2688
|
+
let resp = await fetch(`${baseUrl}/api/runtimes/${encodeURIComponent(doc)}`, {
|
|
2657
2689
|
method: 'DELETE',
|
|
2658
2690
|
});
|
|
2691
|
+
if (!resp.ok && resp.status === 404) {
|
|
2692
|
+
resp = await fetch(`${baseUrl}/api/sessions/${encodeURIComponent(doc)}`, {
|
|
2693
|
+
method: 'DELETE',
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2659
2696
|
if (!resp.ok) {
|
|
2660
|
-
console.warn(`[mrmd.
|
|
2697
|
+
console.warn(`[mrmd.runtime] Failed to destroy runtime: ${resp.statusText}`);
|
|
2661
2698
|
}
|
|
2662
2699
|
} catch (err) {
|
|
2663
|
-
console.warn(`[mrmd.
|
|
2700
|
+
console.warn(`[mrmd.runtime] Failed to destroy runtime:`, err);
|
|
2664
2701
|
}
|
|
2665
2702
|
|
|
2666
2703
|
// Destroy editor
|
|
2667
2704
|
editor.destroy();
|
|
2668
2705
|
};
|
|
2669
2706
|
|
|
2670
|
-
// Add method to get
|
|
2671
|
-
editor.
|
|
2707
|
+
// Add method to get runtime info
|
|
2708
|
+
editor.getRuntimeInfo = function() {
|
|
2672
2709
|
return this._sessionInfo;
|
|
2673
2710
|
};
|
|
2674
2711
|
|
|
2675
2712
|
return editor;
|
|
2676
2713
|
}
|
|
2677
|
-
// #endregion
|
|
2714
|
+
// #endregion RUNTIME
|
|
2678
2715
|
|
|
2679
2716
|
// #region DRIVE
|
|
2680
2717
|
/**
|
|
@@ -3038,7 +3075,7 @@ const mrmd = {
|
|
|
3038
3075
|
version: VERSION,
|
|
3039
3076
|
create,
|
|
3040
3077
|
drive,
|
|
3041
|
-
|
|
3078
|
+
runtime,
|
|
3042
3079
|
yjs,
|
|
3043
3080
|
codemirror,
|
|
3044
3081
|
terminal,
|
|
@@ -3096,7 +3133,7 @@ export default mrmd;
|
|
|
3096
3133
|
export {
|
|
3097
3134
|
create,
|
|
3098
3135
|
drive,
|
|
3099
|
-
|
|
3136
|
+
runtime,
|
|
3100
3137
|
yjs,
|
|
3101
3138
|
codemirror,
|
|
3102
3139
|
terminal,
|
|
@@ -3232,4 +3269,8 @@ export {
|
|
|
3232
3269
|
|
|
3233
3270
|
// Re-export shell components for direct imports
|
|
3234
3271
|
export const { createStudio, OrchestratorClient, Drive, createDrive, ShellStateManager, injectShellStyles } = shellModule;
|
|
3272
|
+
|
|
3273
|
+
// Document language detection and frontmatter updater
|
|
3274
|
+
export { getDocumentLanguages, getLanguageDisplay, isExecutableLanguage } from './document-languages.js';
|
|
3275
|
+
export { parseFrontmatter, updateFrontmatterSession, readFrontmatterSession, getEffectiveSessionConfig } from './frontmatter-updater.js';
|
|
3235
3276
|
// #endregion EXPORTS
|
package/src/markdown/renderer.js
CHANGED
|
@@ -121,6 +121,33 @@ function extractWikiLinks(text) {
|
|
|
121
121
|
return results;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Find YAML frontmatter at the top of the document (--- ... ---).
|
|
126
|
+
*
|
|
127
|
+
* We use this to suppress markdown rendering inside frontmatter since the
|
|
128
|
+
* opening/closing `---` lines are parsed as HorizontalRule nodes by markdown.
|
|
129
|
+
*/
|
|
130
|
+
function findFrontmatterRange(doc) {
|
|
131
|
+
if (doc.lines < 2) return null;
|
|
132
|
+
|
|
133
|
+
const firstLine = doc.line(1);
|
|
134
|
+
if (firstLine.text.trim() !== '---') return null;
|
|
135
|
+
|
|
136
|
+
for (let i = 2; i <= doc.lines; i++) {
|
|
137
|
+
const line = doc.line(i);
|
|
138
|
+
if (line.text.trim() === '---') {
|
|
139
|
+
return {
|
|
140
|
+
from: firstLine.from,
|
|
141
|
+
to: line.to,
|
|
142
|
+
startLine: 1,
|
|
143
|
+
endLine: i,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
124
151
|
/**
|
|
125
152
|
* BlockImageWidget wrapper that caches its rendered height for stable layout.
|
|
126
153
|
*/
|
|
@@ -223,6 +250,7 @@ function buildDecorations(view) {
|
|
|
223
250
|
const doc = view.state.doc;
|
|
224
251
|
const cursorPos = view.state.selection.main.head;
|
|
225
252
|
const cursorLine = doc.lineAt(cursorPos).number;
|
|
253
|
+
const frontmatterRange = findFrontmatterRange(doc);
|
|
226
254
|
|
|
227
255
|
// Get asset resolver from facet (may be null)
|
|
228
256
|
const assetResolver = view.state.facet(assetResolverFacet);
|
|
@@ -249,6 +277,21 @@ function buildDecorations(view) {
|
|
|
249
277
|
to: view.viewport.to,
|
|
250
278
|
enter: (node) => {
|
|
251
279
|
const lineNum = doc.lineAt(node.from).number;
|
|
280
|
+
|
|
281
|
+
// Never apply markdown rendering/styling inside YAML frontmatter.
|
|
282
|
+
// Frontmatter is rendered separately by block-decorations.
|
|
283
|
+
// Use line-based start detection so we still skip nodes that may extend
|
|
284
|
+
// past the line boundary (e.g., HorizontalRule tokens including newline).
|
|
285
|
+
if (frontmatterRange && node.name !== 'Document') {
|
|
286
|
+
const nodeStartLine = doc.lineAt(node.from).number;
|
|
287
|
+
if (
|
|
288
|
+
nodeStartLine >= frontmatterRange.startLine &&
|
|
289
|
+
nodeStartLine <= frontmatterRange.endLine
|
|
290
|
+
) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
252
295
|
const isActiveLine = lineNum === cursorLine;
|
|
253
296
|
|
|
254
297
|
// Marker class: hidden on blur, muted on focus
|
|
@@ -735,6 +778,9 @@ function buildDecorations(view) {
|
|
|
735
778
|
const line = doc.line(i);
|
|
736
779
|
const isActiveLine = i === cursorLine;
|
|
737
780
|
|
|
781
|
+
// Skip frontmatter lines
|
|
782
|
+
if (frontmatterRange && i >= frontmatterRange.startLine && i <= frontmatterRange.endLine) continue;
|
|
783
|
+
|
|
738
784
|
// Skip lines inside code blocks
|
|
739
785
|
if (codeBlockLines.has(i)) continue;
|
|
740
786
|
|
|
@@ -770,6 +816,9 @@ function buildDecorations(view) {
|
|
|
770
816
|
const line = doc.line(i);
|
|
771
817
|
const isActiveLine = i === cursorLine;
|
|
772
818
|
|
|
819
|
+
// Skip frontmatter lines
|
|
820
|
+
if (frontmatterRange && i >= frontmatterRange.startLine && i <= frontmatterRange.endLine) continue;
|
|
821
|
+
|
|
773
822
|
// Skip lines inside code blocks (using syntax tree detection)
|
|
774
823
|
if (codeBlockLines.has(i)) continue;
|
|
775
824
|
|
|
@@ -810,12 +859,12 @@ function buildDecorations(view) {
|
|
|
810
859
|
const line = doc.line(i);
|
|
811
860
|
const isActiveLine = i === cursorLine;
|
|
812
861
|
|
|
862
|
+
// Skip frontmatter lines
|
|
863
|
+
if (frontmatterRange && i >= frontmatterRange.startLine && i <= frontmatterRange.endLine) continue;
|
|
864
|
+
|
|
813
865
|
// Skip lines inside code blocks (using syntax tree detection)
|
|
814
866
|
if (codeBlockLines.has(i)) continue;
|
|
815
867
|
|
|
816
|
-
// Skip frontmatter (YAML between ---)
|
|
817
|
-
// This is a simple check - a full solution would track state
|
|
818
|
-
|
|
819
868
|
const htmlElements = extractHtmlElements(line.text);
|
|
820
869
|
|
|
821
870
|
for (const el of htmlElements) {
|