mrmd-editor 0.5.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/config/schema.js +4 -3
- package/src/ctrl-k-modal.js +190 -14
- package/src/document-languages.js +105 -0
- package/src/execution.js +50 -13
- package/src/frontmatter-updater.js +224 -0
- package/src/index.js +162 -97
- package/src/markdown/block-decorations.js +103 -0
- package/src/markdown/renderer.js +52 -3
- package/src/markdown/styles.js +126 -0
- package/src/markdown/widgets/frontmatter.js +438 -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 +818 -123
- 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
|
@@ -64,6 +64,9 @@ import {
|
|
|
64
64
|
extractDisplayMath,
|
|
65
65
|
generateMathId,
|
|
66
66
|
} from './widgets/math.js';
|
|
67
|
+
import {
|
|
68
|
+
FrontmatterWidget,
|
|
69
|
+
} from './widgets/frontmatter.js';
|
|
67
70
|
|
|
68
71
|
// =============================================================================
|
|
69
72
|
// Height Cache for Stable Layout
|
|
@@ -370,6 +373,64 @@ function findDisplayMathRanges(state) {
|
|
|
370
373
|
return ranges;
|
|
371
374
|
}
|
|
372
375
|
|
|
376
|
+
/**
|
|
377
|
+
* FrontmatterWidget wrapper that caches its rendered height for stable layout.
|
|
378
|
+
*/
|
|
379
|
+
class FrontmatterWidgetWithHeightCache extends FrontmatterWidget {
|
|
380
|
+
constructor(yamlContent, contentHash, sourceFrom, sourceTo) {
|
|
381
|
+
super(yamlContent, contentHash, sourceFrom, sourceTo);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
toDOM(view) {
|
|
385
|
+
const dom = super.toDOM(view);
|
|
386
|
+
const contentHash = this.contentHash;
|
|
387
|
+
|
|
388
|
+
requestAnimationFrame(() => {
|
|
389
|
+
const line = dom.closest('.cm-line');
|
|
390
|
+
const height = line ? line.offsetHeight : dom.offsetHeight;
|
|
391
|
+
if (height > 0) {
|
|
392
|
+
cacheWidgetHeight(contentHash, height);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
return dom;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Find frontmatter range at the start of the document (--- ... ---)
|
|
402
|
+
*/
|
|
403
|
+
function findFrontmatterRange(state) {
|
|
404
|
+
const doc = state.doc;
|
|
405
|
+
if (doc.lines < 2) return null;
|
|
406
|
+
|
|
407
|
+
const firstLine = doc.line(1);
|
|
408
|
+
if (firstLine.text.trim() !== '---') return null;
|
|
409
|
+
|
|
410
|
+
// Find closing ---
|
|
411
|
+
for (let i = 2; i <= doc.lines; i++) {
|
|
412
|
+
const line = doc.line(i);
|
|
413
|
+
if (line.text.trim() === '---') {
|
|
414
|
+
// YAML content is between line 2 and line i-1
|
|
415
|
+
const yamlLines = [];
|
|
416
|
+
for (let j = 2; j < i; j++) {
|
|
417
|
+
yamlLines.push(doc.line(j).text);
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
type: 'frontmatter',
|
|
421
|
+
from: firstLine.from,
|
|
422
|
+
to: line.to,
|
|
423
|
+
startLine: 1,
|
|
424
|
+
endLine: i,
|
|
425
|
+
content: yamlLines.join('\n'),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// If we hit a line that looks like content (not YAML), stop
|
|
429
|
+
// Frontmatter can't contain blank lines followed by markdown
|
|
430
|
+
}
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
|
|
373
434
|
/**
|
|
374
435
|
* Build decorations for all block elements
|
|
375
436
|
*/
|
|
@@ -471,6 +532,48 @@ function buildBlockDecorations(state) {
|
|
|
471
532
|
}
|
|
472
533
|
}
|
|
473
534
|
|
|
535
|
+
// Find and process frontmatter
|
|
536
|
+
const fmRange = findFrontmatterRange(state);
|
|
537
|
+
|
|
538
|
+
if (fmRange) {
|
|
539
|
+
const cursorInFrontmatter = cursorLine >= fmRange.startLine && cursorLine <= fmRange.endLine;
|
|
540
|
+
const contentHash = 'fm-' + hashContent(fmRange.content);
|
|
541
|
+
|
|
542
|
+
if (!cursorInFrontmatter) {
|
|
543
|
+
decorations.push(
|
|
544
|
+
Decoration.replace({
|
|
545
|
+
widget: new FrontmatterWidgetWithHeightCache(
|
|
546
|
+
fmRange.content,
|
|
547
|
+
contentHash,
|
|
548
|
+
fmRange.from,
|
|
549
|
+
fmRange.to
|
|
550
|
+
),
|
|
551
|
+
}).range(fmRange.from, fmRange.to)
|
|
552
|
+
);
|
|
553
|
+
} else {
|
|
554
|
+
// Cursor inside: show raw YAML with spacer for stable height
|
|
555
|
+
const cachedHeight = getCachedHeight(contentHash);
|
|
556
|
+
if (cachedHeight) {
|
|
557
|
+
const lineCount = fmRange.endLine - fmRange.startLine + 1;
|
|
558
|
+
const lineHeight = getLineHeight();
|
|
559
|
+
const rawHeight = lineCount * lineHeight;
|
|
560
|
+
const padding = cachedHeight - rawHeight;
|
|
561
|
+
|
|
562
|
+
if (padding > 0) {
|
|
563
|
+
const lastLine = doc.line(fmRange.endLine);
|
|
564
|
+
decorations.push(
|
|
565
|
+
Decoration.line({
|
|
566
|
+
attributes: {
|
|
567
|
+
class: 'cm-block-spacer-line',
|
|
568
|
+
style: `padding-bottom: ${padding}px`
|
|
569
|
+
}
|
|
570
|
+
}).range(lastLine.from)
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
474
577
|
return Decoration.set(decorations, true);
|
|
475
578
|
}
|
|
476
579
|
|
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) {
|
package/src/markdown/styles.js
CHANGED
|
@@ -1015,6 +1015,132 @@ export const markdownStyles = `
|
|
|
1015
1015
|
.cm-inline-html bdo {
|
|
1016
1016
|
unicode-bidi: bidi-override;
|
|
1017
1017
|
}
|
|
1018
|
+
|
|
1019
|
+
/* ==========================================================================
|
|
1020
|
+
MOBILE RESPONSIVE
|
|
1021
|
+
|
|
1022
|
+
Quarto/Astro-inspired: content reads beautifully on narrow screens.
|
|
1023
|
+
Tables scroll horizontally, images scale, code wraps or scrolls.
|
|
1024
|
+
========================================================================== */
|
|
1025
|
+
|
|
1026
|
+
@media (max-width: 768px) {
|
|
1027
|
+
|
|
1028
|
+
/* Tables: horizontal scroll when they're wider than the viewport.
|
|
1029
|
+
This is the exact pattern Quarto and MkDocs Material use. */
|
|
1030
|
+
.cm-table-widget {
|
|
1031
|
+
overflow-x: auto;
|
|
1032
|
+
-webkit-overflow-scrolling: touch;
|
|
1033
|
+
margin-left: -4px;
|
|
1034
|
+
margin-right: -4px;
|
|
1035
|
+
padding-left: 4px;
|
|
1036
|
+
padding-right: 4px;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/* Subtle fade on the right edge when table overflows */
|
|
1040
|
+
.cm-table-widget::after {
|
|
1041
|
+
content: '';
|
|
1042
|
+
position: sticky;
|
|
1043
|
+
right: 0;
|
|
1044
|
+
display: block;
|
|
1045
|
+
width: 16px;
|
|
1046
|
+
margin-top: -100%;
|
|
1047
|
+
height: 100%;
|
|
1048
|
+
background: linear-gradient(to right, transparent, var(--editor-background, #fff) 80%);
|
|
1049
|
+
pointer-events: none;
|
|
1050
|
+
float: right;
|
|
1051
|
+
opacity: 0.5;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
.cm-table {
|
|
1055
|
+
font-size: 0.88em;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
.cm-table th,
|
|
1059
|
+
.cm-table td {
|
|
1060
|
+
padding: 0.4em 0.65em;
|
|
1061
|
+
white-space: nowrap;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/* Images: full-width, never overflow the screen */
|
|
1065
|
+
.cm-image-block-img {
|
|
1066
|
+
max-width: 100%;
|
|
1067
|
+
max-height: 50vh;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
.cm-image-inline-img {
|
|
1071
|
+
max-width: 100%;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/* Wide images: don't break out of viewport */
|
|
1075
|
+
.cm-image-pos-wide {
|
|
1076
|
+
width: 100%;
|
|
1077
|
+
max-width: 100%;
|
|
1078
|
+
margin-left: 0;
|
|
1079
|
+
margin-right: 0;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.cm-image-pos-wide .cm-image-block-img {
|
|
1083
|
+
max-width: 100%;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/* Right/left aligned images: go full width on mobile */
|
|
1087
|
+
.cm-image-pos-right,
|
|
1088
|
+
.cm-image-pos-left {
|
|
1089
|
+
text-align: center;
|
|
1090
|
+
padding-left: 0;
|
|
1091
|
+
padding-right: 0;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
.cm-image-pos-right .cm-image-block-img,
|
|
1095
|
+
.cm-image-pos-left .cm-image-block-img {
|
|
1096
|
+
max-width: 100%;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/* Display math: horizontal scroll for wide equations */
|
|
1100
|
+
.cm-math-display {
|
|
1101
|
+
overflow-x: auto;
|
|
1102
|
+
-webkit-overflow-scrolling: touch;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/* Blockquotes: slightly tighter */
|
|
1106
|
+
.cm-md-blockquote-line {
|
|
1107
|
+
padding-left: 0.75em;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/* Headings: slightly smaller on mobile for better fit */
|
|
1111
|
+
.cm-md-h1 {
|
|
1112
|
+
font-size: clamp(1.4em, 5vw, var(--md-heading-1-size, 1.75em));
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
.cm-md-h2 {
|
|
1116
|
+
font-size: clamp(1.2em, 4vw, var(--md-heading-2-size, 1.4em));
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/* Horizontal rule: full width */
|
|
1120
|
+
.cm-md-hr-line::after {
|
|
1121
|
+
left: 0;
|
|
1122
|
+
right: 0;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/* Touch-specific: make interactive elements more tappable */
|
|
1127
|
+
@media (pointer: coarse) {
|
|
1128
|
+
|
|
1129
|
+
/* Checkboxes: bigger for finger tapping */
|
|
1130
|
+
.cm-task-checkbox {
|
|
1131
|
+
width: 1.2em;
|
|
1132
|
+
height: 1.2em;
|
|
1133
|
+
margin-right: 0.5em;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/* Links: slightly more padding to enlarge tap area */
|
|
1137
|
+
.cm-md-link-text,
|
|
1138
|
+
.cm-external-link,
|
|
1139
|
+
.cm-file-link,
|
|
1140
|
+
.cm-wiki-link {
|
|
1141
|
+
padding: 2px 0;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1018
1144
|
`;
|
|
1019
1145
|
|
|
1020
1146
|
/**
|