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
package/src/output-widget.js
CHANGED
|
@@ -55,6 +55,222 @@ class HiddenWidget extends WidgetType {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function escapeHtml(text) {
|
|
59
|
+
return String(text ?? '')
|
|
60
|
+
.replace(/&/g, '&')
|
|
61
|
+
.replace(/</g, '<')
|
|
62
|
+
.replace(/>/g, '>')
|
|
63
|
+
.replace(/"/g, '"')
|
|
64
|
+
.replace(/'/g, ''');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function tryParseJsonOutput(content) {
|
|
68
|
+
if (!content || hasAnsi(content)) return null;
|
|
69
|
+
if (content.length > 250_000) return null; // Guard large payloads
|
|
70
|
+
|
|
71
|
+
const trimmed = content.trim();
|
|
72
|
+
if (!trimmed) return null;
|
|
73
|
+
const looksLikeObjectOrArray =
|
|
74
|
+
(trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
75
|
+
(trimmed.startsWith('[') && trimmed.endsWith(']'));
|
|
76
|
+
if (!looksLikeObjectOrArray) return null;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(trimmed);
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function jsonType(value) {
|
|
86
|
+
if (value === null) return 'null';
|
|
87
|
+
if (Array.isArray(value)) return 'array';
|
|
88
|
+
return typeof value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function formatJsonPrimitive(value) {
|
|
92
|
+
const type = jsonType(value);
|
|
93
|
+
if (type === 'string') return `"${value}"`;
|
|
94
|
+
if (type === 'number' || type === 'boolean' || type === 'null') return String(value);
|
|
95
|
+
if (type === 'undefined') return 'undefined';
|
|
96
|
+
return String(value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function summarizeJson(value) {
|
|
100
|
+
const type = jsonType(value);
|
|
101
|
+
if (type === 'array') return `Array(${value.length})`;
|
|
102
|
+
if (type === 'object') return `Object(${Object.keys(value).length})`;
|
|
103
|
+
return type;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function previewJsonValue(value) {
|
|
107
|
+
const type = jsonType(value);
|
|
108
|
+
if (type === 'array') {
|
|
109
|
+
const sample = value.slice(0, 3).map(v => formatJsonPrimitive(v)).join(', ');
|
|
110
|
+
return `[${sample}${value.length > 3 ? ', …' : ''}]`;
|
|
111
|
+
}
|
|
112
|
+
if (type === 'object') {
|
|
113
|
+
const keys = Object.keys(value);
|
|
114
|
+
return `{${keys.slice(0, 3).join(', ')}${keys.length > 3 ? ', …' : ''}}`;
|
|
115
|
+
}
|
|
116
|
+
return formatJsonPrimitive(value);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function buildJsonTreeNode(key, value, depth = 0) {
|
|
120
|
+
const type = jsonType(value);
|
|
121
|
+
const isExpandable = type === 'array' || type === 'object';
|
|
122
|
+
|
|
123
|
+
if (!isExpandable) {
|
|
124
|
+
const row = document.createElement('div');
|
|
125
|
+
row.className = 'cm-json-node cm-json-node-leaf';
|
|
126
|
+
|
|
127
|
+
if (key !== null) {
|
|
128
|
+
const keyEl = document.createElement('span');
|
|
129
|
+
keyEl.className = 'cm-json-key';
|
|
130
|
+
keyEl.textContent = String(key);
|
|
131
|
+
row.appendChild(keyEl);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const valueEl = document.createElement('span');
|
|
135
|
+
valueEl.className = `cm-json-value cm-json-type-${type}`;
|
|
136
|
+
valueEl.textContent = formatJsonPrimitive(value);
|
|
137
|
+
row.appendChild(valueEl);
|
|
138
|
+
return row;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const details = document.createElement('details');
|
|
142
|
+
details.className = 'cm-json-node cm-json-node-expandable';
|
|
143
|
+
details.open = depth < 1;
|
|
144
|
+
|
|
145
|
+
const summary = document.createElement('summary');
|
|
146
|
+
summary.className = 'cm-json-node-summary';
|
|
147
|
+
|
|
148
|
+
if (key !== null) {
|
|
149
|
+
const keyEl = document.createElement('span');
|
|
150
|
+
keyEl.className = 'cm-json-key';
|
|
151
|
+
keyEl.textContent = String(key);
|
|
152
|
+
summary.appendChild(keyEl);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const meta = document.createElement('span');
|
|
156
|
+
meta.className = 'cm-json-meta';
|
|
157
|
+
meta.textContent = summarizeJson(value);
|
|
158
|
+
summary.appendChild(meta);
|
|
159
|
+
|
|
160
|
+
const preview = document.createElement('span');
|
|
161
|
+
preview.className = 'cm-json-preview';
|
|
162
|
+
preview.textContent = previewJsonValue(value);
|
|
163
|
+
summary.appendChild(preview);
|
|
164
|
+
|
|
165
|
+
details.appendChild(summary);
|
|
166
|
+
|
|
167
|
+
const children = document.createElement('div');
|
|
168
|
+
children.className = 'cm-json-children';
|
|
169
|
+
|
|
170
|
+
const entries = Array.isArray(value)
|
|
171
|
+
? value.map((v, i) => [`[${i}]`, v])
|
|
172
|
+
: Object.entries(value);
|
|
173
|
+
|
|
174
|
+
if (entries.length === 0) {
|
|
175
|
+
const empty = document.createElement('div');
|
|
176
|
+
empty.className = 'cm-json-empty';
|
|
177
|
+
empty.textContent = Array.isArray(value) ? '[]' : '{}';
|
|
178
|
+
children.appendChild(empty);
|
|
179
|
+
} else {
|
|
180
|
+
for (const [childKey, childValue] of entries) {
|
|
181
|
+
children.appendChild(buildJsonTreeNode(childKey, childValue, depth + 1));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
details.appendChild(children);
|
|
186
|
+
return details;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function stripCssComments(css) {
|
|
190
|
+
return String(css ?? '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parseCssSelectors(css) {
|
|
194
|
+
const cleaned = stripCssComments(css);
|
|
195
|
+
const selectors = [];
|
|
196
|
+
let ruleCount = 0;
|
|
197
|
+
const ruleRegex = /([^{}]+)\{/g;
|
|
198
|
+
let match;
|
|
199
|
+
|
|
200
|
+
while ((match = ruleRegex.exec(cleaned)) !== null) {
|
|
201
|
+
const selectorGroup = match[1].trim();
|
|
202
|
+
if (!selectorGroup || selectorGroup.startsWith('@')) continue;
|
|
203
|
+
|
|
204
|
+
let addedInRule = 0;
|
|
205
|
+
for (const selector of selectorGroup.split(',')) {
|
|
206
|
+
const trimmed = selector.trim();
|
|
207
|
+
if (!trimmed) continue;
|
|
208
|
+
if (trimmed === 'from' || trimmed === 'to' || /^\d+%$/.test(trimmed)) continue;
|
|
209
|
+
selectors.push(trimmed);
|
|
210
|
+
addedInRule++;
|
|
211
|
+
}
|
|
212
|
+
if (addedInRule > 0) ruleCount++;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { selectors, ruleCount };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function analyzeCssAgainstDocument(css, scope = 'all') {
|
|
219
|
+
return analyzeCssAgainstRoots(css, getCssQueryRoots(scope), scope);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getCssQueryRoots(scope = 'all') {
|
|
223
|
+
const roots = [];
|
|
224
|
+
if (scope === 'all' || scope === 'main') {
|
|
225
|
+
roots.push(document);
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const artifactIframe = document.getElementById('artifact-iframe');
|
|
229
|
+
const artifactDoc = artifactIframe?.contentDocument || artifactIframe?.contentWindow?.document;
|
|
230
|
+
if (artifactDoc && (scope === 'all' || scope === 'artifact')) {
|
|
231
|
+
roots.push(artifactDoc);
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
// Cross-origin or unavailable iframe; ignore.
|
|
235
|
+
}
|
|
236
|
+
return roots;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function analyzeCssAgainstRoots(css, queryRoots, scope = 'all') {
|
|
240
|
+
const { selectors, ruleCount } = parseCssSelectors(css);
|
|
241
|
+
const uniqueSelectors = Array.from(new Set(selectors));
|
|
242
|
+
const maxSelectors = 24;
|
|
243
|
+
const visibleSelectors = uniqueSelectors.slice(0, maxSelectors);
|
|
244
|
+
|
|
245
|
+
const selectorMatches = visibleSelectors.map((selector) => {
|
|
246
|
+
let count = 0;
|
|
247
|
+
try {
|
|
248
|
+
for (const root of queryRoots) {
|
|
249
|
+
count += root.querySelectorAll(selector).length;
|
|
250
|
+
}
|
|
251
|
+
return { selector, count, valid: true };
|
|
252
|
+
} catch {
|
|
253
|
+
return { selector, count: 0, valid: false };
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const matchedSelectors = selectorMatches.filter((s) => s.valid && s.count > 0).length;
|
|
258
|
+
const totalMatches = selectorMatches
|
|
259
|
+
.filter((s) => s.valid)
|
|
260
|
+
.reduce((acc, s) => acc + s.count, 0);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
ruleCount,
|
|
264
|
+
totalSelectors: uniqueSelectors.length,
|
|
265
|
+
selectorMatches,
|
|
266
|
+
matchedSelectors,
|
|
267
|
+
totalMatches,
|
|
268
|
+
scope,
|
|
269
|
+
rootsReady: queryRoots.length > 0,
|
|
270
|
+
truncated: uniqueSelectors.length > maxSelectors,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
58
274
|
// =============================================================================
|
|
59
275
|
// Height Cache for Stable Layout (prevents jitter when editing output blocks)
|
|
60
276
|
// =============================================================================
|
|
@@ -330,8 +546,7 @@ class HtmlOutputWidget extends WidgetType {
|
|
|
330
546
|
}
|
|
331
547
|
|
|
332
548
|
/**
|
|
333
|
-
* Widget for rendering CSS output
|
|
334
|
-
* Injects CSS into a scoped container and shows a preview.
|
|
549
|
+
* Widget for rendering CSS output as compact impact summary.
|
|
335
550
|
*/
|
|
336
551
|
class CssOutputWidget extends WidgetType {
|
|
337
552
|
/**
|
|
@@ -340,16 +555,21 @@ class CssOutputWidget extends WidgetType {
|
|
|
340
555
|
* @param {number} blockStart - Document position where this output block starts
|
|
341
556
|
* @param {string|null} execId - Execution ID for this output block
|
|
342
557
|
*/
|
|
343
|
-
constructor(content, hidden = false, blockStart = 0, execId = null) {
|
|
558
|
+
constructor(content, hidden = false, blockStart = 0, execId = null, targetScope = 'all') {
|
|
344
559
|
super();
|
|
345
560
|
this.content = content;
|
|
346
561
|
this.hidden = hidden;
|
|
347
562
|
this.blockStart = blockStart;
|
|
348
563
|
this.execId = execId;
|
|
564
|
+
this.targetScope = targetScope;
|
|
349
565
|
}
|
|
350
566
|
|
|
351
567
|
eq(other) {
|
|
352
|
-
return other.content === this.content &&
|
|
568
|
+
return other.content === this.content &&
|
|
569
|
+
other.hidden === this.hidden &&
|
|
570
|
+
other.blockStart === this.blockStart &&
|
|
571
|
+
other.execId === this.execId &&
|
|
572
|
+
other.targetScope === this.targetScope;
|
|
353
573
|
}
|
|
354
574
|
|
|
355
575
|
toDOM() {
|
|
@@ -360,81 +580,249 @@ class CssOutputWidget extends WidgetType {
|
|
|
360
580
|
container.dataset.execId = this.execId;
|
|
361
581
|
}
|
|
362
582
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
.join(', ');
|
|
391
|
-
return `${scoped} {`;
|
|
392
|
-
}
|
|
393
|
-
);
|
|
394
|
-
style.textContent = scopedCss;
|
|
395
|
-
|
|
396
|
-
// Inject the style into document head
|
|
397
|
-
document.head.appendChild(style);
|
|
398
|
-
|
|
399
|
-
// Create preview container
|
|
400
|
-
const preview = document.createElement('div');
|
|
401
|
-
preview.className = `cm-css-preview ${scopeClass}`;
|
|
402
|
-
preview.innerHTML = `
|
|
403
|
-
<div class="cm-css-preview-header">
|
|
404
|
-
<span class="cm-css-preview-badge">CSS Applied</span>
|
|
405
|
-
<span class="cm-css-preview-info">${this.content.split('{').length - 1} rules</span>
|
|
406
|
-
</div>
|
|
407
|
-
<div class="cm-css-preview-demo">
|
|
408
|
-
<p>Preview text with <strong>bold</strong> and <em>italic</em></p>
|
|
409
|
-
<button>Button</button>
|
|
410
|
-
<a href="#">Link</a>
|
|
411
|
-
<div class="box">Box element</div>
|
|
412
|
-
</div>
|
|
583
|
+
const header = document.createElement('div');
|
|
584
|
+
header.className = 'cm-css-header';
|
|
585
|
+
const badge = document.createElement('span');
|
|
586
|
+
badge.className = 'cm-css-badge';
|
|
587
|
+
badge.textContent = 'CSS';
|
|
588
|
+
const statsEl = document.createElement('span');
|
|
589
|
+
statsEl.className = 'cm-css-stats';
|
|
590
|
+
const totalTargetsEl = document.createElement('span');
|
|
591
|
+
totalTargetsEl.className = 'cm-css-total-targets';
|
|
592
|
+
header.appendChild(badge);
|
|
593
|
+
header.appendChild(statsEl);
|
|
594
|
+
header.appendChild(totalTargetsEl);
|
|
595
|
+
container.appendChild(header);
|
|
596
|
+
|
|
597
|
+
const selectorList = document.createElement('div');
|
|
598
|
+
selectorList.className = 'cm-css-chip-list';
|
|
599
|
+
container.appendChild(selectorList);
|
|
600
|
+
|
|
601
|
+
const note = document.createElement('div');
|
|
602
|
+
note.className = 'cm-css-note';
|
|
603
|
+
container.appendChild(note);
|
|
604
|
+
|
|
605
|
+
const sourceDetails = document.createElement('details');
|
|
606
|
+
sourceDetails.className = 'cm-css-source';
|
|
607
|
+
sourceDetails.innerHTML = `
|
|
608
|
+
<summary>Show CSS</summary>
|
|
609
|
+
<pre class="cm-css-source-code">${escapeHtml(this.content)}</pre>
|
|
413
610
|
`;
|
|
611
|
+
container.appendChild(sourceDetails);
|
|
612
|
+
|
|
613
|
+
const renderAnalysis = (analysis) => {
|
|
614
|
+
const statsText = analysis.ruleCount > 0
|
|
615
|
+
? `${analysis.ruleCount} rule${analysis.ruleCount === 1 ? '' : 's'} · ${analysis.matchedSelectors}/${analysis.totalSelectors} selectors match`
|
|
616
|
+
: 'No parseable CSS rules detected';
|
|
617
|
+
statsEl.textContent = statsText;
|
|
618
|
+
totalTargetsEl.textContent = `${analysis.totalMatches} target${analysis.totalMatches === 1 ? '' : 's'}`;
|
|
619
|
+
|
|
620
|
+
selectorList.innerHTML = '';
|
|
621
|
+
if (analysis.selectorMatches.length === 0) {
|
|
622
|
+
const empty = document.createElement('div');
|
|
623
|
+
empty.className = 'cm-css-note';
|
|
624
|
+
empty.textContent = 'Run a CSS cell to see selector impact.';
|
|
625
|
+
selectorList.appendChild(empty);
|
|
626
|
+
} else {
|
|
627
|
+
for (const item of analysis.selectorMatches) {
|
|
628
|
+
const chip = document.createElement('div');
|
|
629
|
+
const stateClass = item.valid ? (item.count > 0 ? 'cm-css-chip-match' : 'cm-css-chip-nomatch') : 'cm-css-chip-invalid';
|
|
630
|
+
chip.className = `cm-css-chip ${stateClass}`;
|
|
631
|
+
chip.title = item.valid
|
|
632
|
+
? `${item.count} match${item.count === 1 ? '' : 'es'} in document/artifact`
|
|
633
|
+
: 'Invalid selector';
|
|
634
|
+
chip.innerHTML = `
|
|
635
|
+
<code class="cm-css-chip-selector">${escapeHtml(item.selector)}</code>
|
|
636
|
+
<span class="cm-css-chip-count">${item.valid ? item.count : '!'}</span>
|
|
637
|
+
`;
|
|
638
|
+
selectorList.appendChild(chip);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (analysis.truncated) {
|
|
643
|
+
note.textContent = `Showing first ${analysis.selectorMatches.length} selectors.`;
|
|
644
|
+
note.style.display = '';
|
|
645
|
+
} else if (analysis.scope === 'artifact' && !analysis.rootsReady) {
|
|
646
|
+
note.textContent = 'Artifact preview is not ready yet.';
|
|
647
|
+
note.style.display = '';
|
|
648
|
+
} else {
|
|
649
|
+
note.textContent = '';
|
|
650
|
+
note.style.display = 'none';
|
|
651
|
+
}
|
|
652
|
+
};
|
|
414
653
|
|
|
415
|
-
|
|
654
|
+
let refreshPending = false;
|
|
655
|
+
const queueRefresh = () => {
|
|
656
|
+
if (refreshPending) return;
|
|
657
|
+
refreshPending = true;
|
|
658
|
+
requestAnimationFrame(() => {
|
|
659
|
+
refreshPending = false;
|
|
660
|
+
if (!container.isConnected) return;
|
|
661
|
+
renderAnalysis(analyzeCssAgainstDocument(this.content, this.targetScope));
|
|
662
|
+
});
|
|
663
|
+
};
|
|
416
664
|
|
|
417
|
-
//
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
665
|
+
// Keep counts live as HTML/artifact DOM updates.
|
|
666
|
+
const rootObserver = new MutationObserver((mutations) => {
|
|
667
|
+
let relevant = false;
|
|
668
|
+
for (const m of mutations) {
|
|
669
|
+
const target = /** @type {Node|null} */ (m.target);
|
|
670
|
+
if (!target) {
|
|
671
|
+
relevant = true;
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
if (target === container || container.contains(target)) {
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (!container.contains(target)) {
|
|
678
|
+
relevant = true;
|
|
679
|
+
break;
|
|
426
680
|
}
|
|
427
681
|
}
|
|
682
|
+
if (relevant) queueRefresh();
|
|
428
683
|
});
|
|
684
|
+
if (document.documentElement && (this.targetScope === 'all' || this.targetScope === 'main')) {
|
|
685
|
+
rootObserver.observe(document.documentElement, { childList: true, subtree: true });
|
|
686
|
+
}
|
|
429
687
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
688
|
+
let artifactObserver = null;
|
|
689
|
+
let artifactIframe = null;
|
|
690
|
+
let artifactLoadHandler = null;
|
|
691
|
+
try {
|
|
692
|
+
artifactIframe = document.getElementById('artifact-iframe');
|
|
693
|
+
const bindArtifactObserver = () => {
|
|
694
|
+
artifactObserver?.disconnect();
|
|
695
|
+
const artifactDoc = artifactIframe?.contentDocument || artifactIframe?.contentWindow?.document;
|
|
696
|
+
const artifactRoot = artifactDoc?.documentElement || artifactDoc?.body;
|
|
697
|
+
if (!artifactRoot) return;
|
|
698
|
+
artifactObserver = new MutationObserver(() => queueRefresh());
|
|
699
|
+
artifactObserver.observe(artifactRoot, { childList: true, subtree: true, characterData: true });
|
|
700
|
+
};
|
|
701
|
+
if (artifactIframe) {
|
|
702
|
+
artifactLoadHandler = () => {
|
|
703
|
+
bindArtifactObserver();
|
|
704
|
+
queueRefresh();
|
|
705
|
+
};
|
|
706
|
+
artifactIframe.addEventListener('load', artifactLoadHandler);
|
|
707
|
+
}
|
|
708
|
+
if (this.targetScope === 'all' || this.targetScope === 'artifact') {
|
|
709
|
+
bindArtifactObserver();
|
|
710
|
+
}
|
|
711
|
+
} catch {
|
|
712
|
+
// Ignore inaccessible artifact iframe.
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const removalObserver = new MutationObserver(() => {
|
|
716
|
+
if (container.isConnected) return;
|
|
717
|
+
rootObserver.disconnect();
|
|
718
|
+
artifactObserver?.disconnect();
|
|
719
|
+
if (artifactIframe && artifactLoadHandler) {
|
|
720
|
+
artifactIframe.removeEventListener('load', artifactLoadHandler);
|
|
437
721
|
}
|
|
722
|
+
removalObserver.disconnect();
|
|
723
|
+
});
|
|
724
|
+
if (document.body) {
|
|
725
|
+
removalObserver.observe(document.body, { childList: true, subtree: true });
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
renderAnalysis(analyzeCssAgainstDocument(this.content, this.targetScope));
|
|
729
|
+
// Artifact updates can race cell rendering; refresh shortly after mount.
|
|
730
|
+
setTimeout(queueRefresh, 120);
|
|
731
|
+
setTimeout(queueRefresh, 500);
|
|
732
|
+
|
|
733
|
+
return container;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
ignoreEvent() {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Widget for rendering JSON output with an expandable tree.
|
|
743
|
+
*/
|
|
744
|
+
class JsonOutputWidget extends WidgetType {
|
|
745
|
+
/**
|
|
746
|
+
* @param {string} content - JSON content
|
|
747
|
+
* @param {boolean} hidden - Whether widget should be hidden
|
|
748
|
+
* @param {number} blockStart - Document position where this output block starts
|
|
749
|
+
* @param {string|null} execId - Execution ID for this output block
|
|
750
|
+
*/
|
|
751
|
+
constructor(content, hidden = false, blockStart = 0, execId = null) {
|
|
752
|
+
super();
|
|
753
|
+
this.content = content;
|
|
754
|
+
this.hidden = hidden;
|
|
755
|
+
this.blockStart = blockStart;
|
|
756
|
+
this.execId = execId;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
eq(other) {
|
|
760
|
+
return other.content === this.content &&
|
|
761
|
+
other.hidden === this.hidden &&
|
|
762
|
+
other.blockStart === this.blockStart &&
|
|
763
|
+
other.execId === this.execId;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
toDOM() {
|
|
767
|
+
const container = document.createElement('div');
|
|
768
|
+
container.className = 'cm-json-output-widget' + (this.hidden ? ' cm-output-widget-hidden' : '');
|
|
769
|
+
container.dataset.outputBlockStart = String(this.blockStart);
|
|
770
|
+
if (this.execId) {
|
|
771
|
+
container.dataset.execId = this.execId;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const parsed = tryParseJsonOutput(this.content);
|
|
775
|
+
if (parsed === null) {
|
|
776
|
+
container.innerHTML = `<pre class="cm-json-fallback">${escapeHtml(this.content)}</pre>`;
|
|
777
|
+
return container;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const header = document.createElement('div');
|
|
781
|
+
header.className = 'cm-json-header';
|
|
782
|
+
header.innerHTML = `
|
|
783
|
+
<span class="cm-json-badge">JSON</span>
|
|
784
|
+
<span class="cm-json-summary">${escapeHtml(summarizeJson(parsed))}</span>
|
|
785
|
+
<div class="cm-json-actions">
|
|
786
|
+
<button type="button" class="cm-json-action" data-action="expand">Expand</button>
|
|
787
|
+
<button type="button" class="cm-json-action" data-action="collapse">Collapse</button>
|
|
788
|
+
<button type="button" class="cm-json-action" data-action="copy">Copy</button>
|
|
789
|
+
</div>
|
|
790
|
+
`;
|
|
791
|
+
container.appendChild(header);
|
|
792
|
+
|
|
793
|
+
const tree = document.createElement('div');
|
|
794
|
+
tree.className = 'cm-json-tree';
|
|
795
|
+
tree.appendChild(buildJsonTreeNode(null, parsed));
|
|
796
|
+
container.appendChild(tree);
|
|
797
|
+
|
|
798
|
+
const actionButtons = header.querySelectorAll('.cm-json-action');
|
|
799
|
+
actionButtons.forEach((btn) => {
|
|
800
|
+
btn.addEventListener('click', (e) => {
|
|
801
|
+
e.preventDefault();
|
|
802
|
+
e.stopPropagation();
|
|
803
|
+
const action = btn.getAttribute('data-action');
|
|
804
|
+
if (action === 'expand') {
|
|
805
|
+
tree.querySelectorAll('details').forEach((d) => { d.open = true; });
|
|
806
|
+
} else if (action === 'collapse') {
|
|
807
|
+
let first = true;
|
|
808
|
+
tree.querySelectorAll('details').forEach((d) => {
|
|
809
|
+
if (first) {
|
|
810
|
+
d.open = true;
|
|
811
|
+
first = false;
|
|
812
|
+
} else {
|
|
813
|
+
d.open = false;
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
} else if (action === 'copy') {
|
|
817
|
+
navigator.clipboard.writeText(JSON.stringify(parsed, null, 2)).then(() => {
|
|
818
|
+
const previous = btn.textContent;
|
|
819
|
+
btn.textContent = 'Copied';
|
|
820
|
+
setTimeout(() => {
|
|
821
|
+
btn.textContent = previous;
|
|
822
|
+
}, 1200);
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
});
|
|
438
826
|
});
|
|
439
827
|
|
|
440
828
|
return container;
|
|
@@ -606,16 +994,26 @@ function buildDecorations(view, awarenessSystem) {
|
|
|
606
994
|
const blockStart = match.index;
|
|
607
995
|
const blockEnd = blockStart + match[0].length;
|
|
608
996
|
|
|
609
|
-
// Parse execId
|
|
997
|
+
// Parse execId + output metadata from fence line.
|
|
998
|
+
// Supported formats:
|
|
999
|
+
// - output:execId
|
|
1000
|
+
// - output:execId:css
|
|
1001
|
+
// - output:execId:css:artifact
|
|
1002
|
+
// - output:execId:css:main
|
|
610
1003
|
let execId = rawExecId;
|
|
611
|
-
let outputType = null; // 'html', 'css', or null for regular output
|
|
612
|
-
|
|
1004
|
+
let outputType = null; // 'html', 'css', 'json', or null for regular output
|
|
1005
|
+
let cssTargetScope = 'all'; // 'all' | 'main' | 'artifact'
|
|
1006
|
+
if (rawExecId) {
|
|
613
1007
|
const parts = rawExecId.split(':');
|
|
614
|
-
|
|
615
|
-
const
|
|
616
|
-
if (
|
|
617
|
-
|
|
618
|
-
|
|
1008
|
+
execId = parts[0] || rawExecId;
|
|
1009
|
+
const tags = parts.slice(1).map(p => p.toLowerCase());
|
|
1010
|
+
if (tags.includes('html') || tags.includes('htm')) outputType = 'html';
|
|
1011
|
+
else if (tags.includes('json')) outputType = 'json';
|
|
1012
|
+
else if (tags.includes('css') || tags.includes('style') || tags.includes('stylesheet')) outputType = 'css';
|
|
1013
|
+
|
|
1014
|
+
if (outputType === 'css') {
|
|
1015
|
+
if (tags.includes('artifact')) cssTargetScope = 'artifact';
|
|
1016
|
+
else if (tags.includes('main')) cssTargetScope = 'main';
|
|
619
1017
|
}
|
|
620
1018
|
}
|
|
621
1019
|
|
|
@@ -648,6 +1046,10 @@ function buildDecorations(view, awarenessSystem) {
|
|
|
648
1046
|
// Check if output is empty (just whitespace)
|
|
649
1047
|
const trimmedContent = content.trim();
|
|
650
1048
|
const isEmpty = trimmedContent.length === 0;
|
|
1049
|
+
const parsedJson = !isEmpty && (outputType === null || outputType === 'json')
|
|
1050
|
+
? tryParseJsonOutput(trimmedContent)
|
|
1051
|
+
: null;
|
|
1052
|
+
const shouldRenderJson = parsedJson !== null;
|
|
651
1053
|
|
|
652
1054
|
if (anyCollaboratorFocused) {
|
|
653
1055
|
// EDITING MODE: Keep ANSI colors rendered, but make escape sequences
|
|
@@ -711,15 +1113,25 @@ function buildDecorations(view, awarenessSystem) {
|
|
|
711
1113
|
// VIEWING MODE: Render output visually
|
|
712
1114
|
// For HTML/CSS, use rich widgets; for regular output, use ANSI styling
|
|
713
1115
|
|
|
714
|
-
// Style the fence lines (opening and closing fences)
|
|
1116
|
+
// Style the fence lines (opening and closing fences).
|
|
1117
|
+
// Rich output widgets (HTML/CSS, including Mermaid rendered as HTML) are
|
|
1118
|
+
// attached to the opening fence line, so that line must remain unclipped.
|
|
1119
|
+
const richOutput = outputType === 'html' || outputType === 'css' || shouldRenderJson;
|
|
1120
|
+
const startFenceClass = richOutput
|
|
1121
|
+
? 'cm-output-fence-line cm-output-fence-start cm-output-fence-rich-start'
|
|
1122
|
+
: 'cm-output-fence-line cm-output-fence-start';
|
|
1123
|
+
const endFenceClass = richOutput
|
|
1124
|
+
? 'cm-output-fence-line cm-output-fence-end cm-output-fence-rich-end'
|
|
1125
|
+
: 'cm-output-fence-line cm-output-fence-end';
|
|
1126
|
+
|
|
715
1127
|
decorations.push(
|
|
716
1128
|
Decoration.line({
|
|
717
|
-
class:
|
|
1129
|
+
class: startFenceClass,
|
|
718
1130
|
}).range(startLine.from)
|
|
719
1131
|
);
|
|
720
1132
|
decorations.push(
|
|
721
1133
|
Decoration.line({
|
|
722
|
-
class:
|
|
1134
|
+
class: endFenceClass,
|
|
723
1135
|
}).range(endLine.from)
|
|
724
1136
|
);
|
|
725
1137
|
|
|
@@ -742,6 +1154,23 @@ function buildDecorations(view, awarenessSystem) {
|
|
|
742
1154
|
side: 1,
|
|
743
1155
|
}).range(startLine.to)
|
|
744
1156
|
);
|
|
1157
|
+
} else if (shouldRenderJson) {
|
|
1158
|
+
// JSON OUTPUT: Hide content lines and add expandable JSON tree widget
|
|
1159
|
+
for (let i = startLine.number + 1; i < endLine.number; i++) {
|
|
1160
|
+
const line = doc.line(i);
|
|
1161
|
+
decorations.push(
|
|
1162
|
+
Decoration.line({
|
|
1163
|
+
class: 'cm-output-content-line cm-rich-output-hidden',
|
|
1164
|
+
}).range(line.from)
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
decorations.push(
|
|
1169
|
+
Decoration.widget({
|
|
1170
|
+
widget: new JsonOutputWidget(trimmedContent, false, blockStart, execId),
|
|
1171
|
+
side: 1,
|
|
1172
|
+
}).range(startLine.to)
|
|
1173
|
+
);
|
|
745
1174
|
} else if (outputType === 'css' && !isEmpty) {
|
|
746
1175
|
// CSS OUTPUT: Hide content lines and add CSS widget
|
|
747
1176
|
for (let i = startLine.number + 1; i < endLine.number; i++) {
|
|
@@ -756,7 +1185,7 @@ function buildDecorations(view, awarenessSystem) {
|
|
|
756
1185
|
// Add CSS preview widget
|
|
757
1186
|
decorations.push(
|
|
758
1187
|
Decoration.widget({
|
|
759
|
-
widget: new CssOutputWidget(trimmedContent, false, blockStart, execId),
|
|
1188
|
+
widget: new CssOutputWidget(trimmedContent, false, blockStart, execId, cssTargetScope),
|
|
760
1189
|
side: 1,
|
|
761
1190
|
}).range(startLine.to)
|
|
762
1191
|
);
|
|
@@ -1052,6 +1481,14 @@ export const outputWidgetStyles = `
|
|
|
1052
1481
|
margin: 0 !important;
|
|
1053
1482
|
}
|
|
1054
1483
|
|
|
1484
|
+
/* Rich output widgets (HTML/CSS/Mermaid->HTML) are mounted on the opening
|
|
1485
|
+
* fence line. Keep that line unclipped so the inline widget can paint. */
|
|
1486
|
+
.cm-output-fence-rich-start {
|
|
1487
|
+
height: auto !important;
|
|
1488
|
+
overflow: visible !important;
|
|
1489
|
+
line-height: 1 !important;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1055
1492
|
/* Hide CodeMirror's special character rendering (escape symbols) in output blocks */
|
|
1056
1493
|
/* CM renders control chars like ESC as visible ␛ symbols - we hide these since ANSI is rendered via colors */
|
|
1057
1494
|
.cm-output-content-line .cm-specialChar {
|
|
@@ -1416,80 +1853,284 @@ export const outputWidgetStyles = `
|
|
|
1416
1853
|
border-radius: 4px;
|
|
1417
1854
|
}
|
|
1418
1855
|
|
|
1419
|
-
/* CSS Output Widget -
|
|
1856
|
+
/* CSS Output Widget - compact selector impact summary */
|
|
1420
1857
|
.cm-css-output-widget {
|
|
1421
1858
|
position: relative;
|
|
1422
1859
|
margin: 8px 0;
|
|
1423
1860
|
padding: 0;
|
|
1424
1861
|
background: var(--widget-surface, rgba(0, 0, 0, 0.35));
|
|
1425
1862
|
border-radius: var(--widget-border-radius, 6px);
|
|
1863
|
+
border: 1px solid var(--widget-border, rgba(255, 255, 255, 0.08));
|
|
1864
|
+
border-left: 2px solid var(--widget-accent-css, #64b5f6);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
.cm-css-header {
|
|
1868
|
+
display: flex;
|
|
1869
|
+
align-items: center;
|
|
1870
|
+
gap: 8px;
|
|
1871
|
+
padding: 8px 10px;
|
|
1872
|
+
border-bottom: 1px solid var(--widget-border, rgba(255, 255, 255, 0.08));
|
|
1873
|
+
background: var(--widget-surface-elevated, rgba(255, 255, 255, 0.02));
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
.cm-css-badge {
|
|
1877
|
+
font-size: 10px;
|
|
1878
|
+
color: var(--widget-accent-css, #64b5f6);
|
|
1879
|
+
background: color-mix(in srgb, var(--widget-accent-css, #64b5f6) 16%, transparent);
|
|
1880
|
+
border: 1px solid color-mix(in srgb, var(--widget-accent-css, #64b5f6) 35%, transparent);
|
|
1881
|
+
padding: 2px 6px;
|
|
1882
|
+
border-radius: 3px;
|
|
1883
|
+
font-family: var(--widget-font-mono, monospace);
|
|
1884
|
+
text-transform: uppercase;
|
|
1885
|
+
letter-spacing: 0.4px;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
.cm-css-stats {
|
|
1889
|
+
font-size: 11px;
|
|
1890
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.72));
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
.cm-css-total-targets {
|
|
1894
|
+
margin-left: auto;
|
|
1895
|
+
font-size: 11px;
|
|
1896
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.55));
|
|
1897
|
+
font-family: var(--widget-font-mono, monospace);
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
.cm-css-chip-list {
|
|
1901
|
+
display: flex;
|
|
1902
|
+
flex-wrap: wrap;
|
|
1903
|
+
gap: 6px;
|
|
1904
|
+
padding: 8px 10px 4px 10px;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
.cm-css-chip {
|
|
1908
|
+
display: inline-flex;
|
|
1909
|
+
align-items: center;
|
|
1910
|
+
gap: 6px;
|
|
1911
|
+
max-width: 100%;
|
|
1912
|
+
border: 1px solid var(--widget-border, rgba(255, 255, 255, 0.14));
|
|
1913
|
+
border-radius: 999px;
|
|
1914
|
+
padding: 2px 8px;
|
|
1915
|
+
font-size: 11px;
|
|
1916
|
+
background: var(--widget-surface-inset, rgba(255, 255, 255, 0.03));
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
.cm-css-chip-selector {
|
|
1920
|
+
color: var(--widget-text, #e0e0e0);
|
|
1921
|
+
font-family: var(--widget-font-mono, monospace);
|
|
1922
|
+
white-space: nowrap;
|
|
1426
1923
|
overflow: hidden;
|
|
1427
|
-
|
|
1428
|
-
|
|
1924
|
+
text-overflow: ellipsis;
|
|
1925
|
+
max-width: 360px;
|
|
1429
1926
|
}
|
|
1430
1927
|
|
|
1431
|
-
.cm-css-
|
|
1432
|
-
|
|
1928
|
+
.cm-css-chip-count {
|
|
1929
|
+
font-family: var(--widget-font-mono, monospace);
|
|
1930
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.7));
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
.cm-css-chip-match {
|
|
1934
|
+
border-color: color-mix(in srgb, #22c55e 45%, transparent);
|
|
1935
|
+
background: color-mix(in srgb, #22c55e 12%, transparent);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
.cm-css-chip-nomatch {
|
|
1939
|
+
border-color: color-mix(in srgb, #94a3b8 35%, transparent);
|
|
1433
1940
|
}
|
|
1434
1941
|
|
|
1435
|
-
.cm-css-
|
|
1942
|
+
.cm-css-chip-invalid {
|
|
1943
|
+
border-color: color-mix(in srgb, #ef4444 45%, transparent);
|
|
1944
|
+
background: color-mix(in srgb, #ef4444 12%, transparent);
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
.cm-css-note {
|
|
1948
|
+
padding: 2px 10px 8px 10px;
|
|
1949
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.55));
|
|
1950
|
+
font-size: 11px;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
.cm-css-source {
|
|
1954
|
+
margin: 0;
|
|
1955
|
+
border-top: 1px solid var(--widget-border, rgba(255, 255, 255, 0.08));
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
.cm-css-source > summary {
|
|
1959
|
+
cursor: pointer;
|
|
1960
|
+
padding: 7px 10px;
|
|
1961
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.75));
|
|
1962
|
+
font-size: 11px;
|
|
1963
|
+
font-family: var(--widget-font-mono, monospace);
|
|
1964
|
+
user-select: none;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
.cm-css-source > summary:hover {
|
|
1968
|
+
color: var(--widget-text, #e0e0e0);
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
.cm-css-source-code {
|
|
1972
|
+
margin: 0;
|
|
1973
|
+
padding: 0 10px 10px 10px;
|
|
1974
|
+
white-space: pre-wrap;
|
|
1975
|
+
word-break: break-word;
|
|
1976
|
+
color: var(--widget-text, #e0e0e0);
|
|
1977
|
+
font-size: 12px;
|
|
1978
|
+
font-family: var(--widget-font-mono, monospace);
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
/* JSON Output Widget - expandable tree view */
|
|
1982
|
+
.cm-json-output-widget {
|
|
1983
|
+
position: relative;
|
|
1984
|
+
margin: 8px 0;
|
|
1985
|
+
background: var(--widget-surface, rgba(0, 0, 0, 0.35));
|
|
1986
|
+
border: 1px solid var(--widget-border, rgba(255, 255, 255, 0.1));
|
|
1987
|
+
border-left: 3px solid var(--widget-accent-json, #8cc0ff);
|
|
1988
|
+
border-radius: var(--widget-border-radius, 6px);
|
|
1989
|
+
overflow: hidden;
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
.cm-json-header {
|
|
1436
1993
|
display: flex;
|
|
1437
1994
|
align-items: center;
|
|
1438
1995
|
gap: 8px;
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1996
|
+
padding: 8px 10px;
|
|
1997
|
+
border-bottom: 1px solid var(--widget-border, rgba(255, 255, 255, 0.08));
|
|
1998
|
+
background: var(--widget-surface-elevated, rgba(255, 255, 255, 0.02));
|
|
1442
1999
|
}
|
|
1443
2000
|
|
|
1444
|
-
.cm-
|
|
2001
|
+
.cm-json-badge {
|
|
1445
2002
|
font-size: 10px;
|
|
1446
|
-
color: var(--widget-accent-
|
|
1447
|
-
background:
|
|
1448
|
-
|
|
2003
|
+
color: var(--widget-accent-json, #8cc0ff);
|
|
2004
|
+
background: color-mix(in srgb, var(--widget-accent-json, #8cc0ff) 16%, transparent);
|
|
2005
|
+
border: 1px solid color-mix(in srgb, var(--widget-accent-json, #8cc0ff) 35%, transparent);
|
|
1449
2006
|
border-radius: 3px;
|
|
1450
|
-
|
|
2007
|
+
padding: 2px 6px;
|
|
2008
|
+
letter-spacing: 0.4px;
|
|
1451
2009
|
text-transform: uppercase;
|
|
1452
|
-
|
|
2010
|
+
font-family: var(--widget-font-mono, monospace);
|
|
1453
2011
|
}
|
|
1454
2012
|
|
|
1455
|
-
.cm-
|
|
2013
|
+
.cm-json-summary {
|
|
2014
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.65));
|
|
1456
2015
|
font-size: 11px;
|
|
1457
|
-
color: var(--widget-text-muted, rgba(255, 255, 255, 0.5));
|
|
1458
2016
|
}
|
|
1459
2017
|
|
|
1460
|
-
.cm-
|
|
1461
|
-
|
|
1462
|
-
|
|
2018
|
+
.cm-json-actions {
|
|
2019
|
+
margin-left: auto;
|
|
2020
|
+
display: flex;
|
|
2021
|
+
gap: 6px;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
.cm-json-action {
|
|
2025
|
+
background: var(--widget-surface-inset, rgba(255, 255, 255, 0.04));
|
|
2026
|
+
border: 1px solid var(--widget-border, rgba(255, 255, 255, 0.14));
|
|
2027
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.75));
|
|
1463
2028
|
border-radius: 4px;
|
|
1464
|
-
|
|
1465
|
-
font-
|
|
1466
|
-
|
|
2029
|
+
padding: 2px 7px;
|
|
2030
|
+
font-size: 11px;
|
|
2031
|
+
cursor: pointer;
|
|
2032
|
+
font-family: var(--widget-font-mono, monospace);
|
|
1467
2033
|
}
|
|
1468
2034
|
|
|
1469
|
-
.cm-
|
|
1470
|
-
|
|
2035
|
+
.cm-json-action:hover {
|
|
2036
|
+
background: var(--widget-surface-hover, rgba(255, 255, 255, 0.08));
|
|
2037
|
+
color: var(--widget-text, #e0e0e0);
|
|
1471
2038
|
}
|
|
1472
2039
|
|
|
1473
|
-
.cm-
|
|
1474
|
-
padding:
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
2040
|
+
.cm-json-tree {
|
|
2041
|
+
padding: 8px 10px 10px 10px;
|
|
2042
|
+
font-family: var(--widget-font-mono, 'SF Mono', Monaco, monospace);
|
|
2043
|
+
font-size: 12px;
|
|
2044
|
+
color: var(--widget-text, #e0e0e0);
|
|
2045
|
+
max-height: 420px;
|
|
2046
|
+
overflow: auto;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
.cm-json-node {
|
|
2050
|
+
margin-left: 0;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
.cm-json-node summary {
|
|
2054
|
+
list-style: none;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
.cm-json-node summary::-webkit-details-marker {
|
|
2058
|
+
display: none;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
.cm-json-node-summary {
|
|
1479
2062
|
cursor: pointer;
|
|
2063
|
+
display: flex;
|
|
2064
|
+
align-items: baseline;
|
|
2065
|
+
gap: 8px;
|
|
2066
|
+
padding: 2px 0;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
.cm-json-node-summary::before {
|
|
2070
|
+
content: '▶';
|
|
2071
|
+
font-size: 9px;
|
|
2072
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.5));
|
|
2073
|
+
margin-right: 2px;
|
|
2074
|
+
transform: translateY(-1px);
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
.cm-json-node[open] > .cm-json-node-summary::before {
|
|
2078
|
+
content: '▼';
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
.cm-json-node-leaf {
|
|
2082
|
+
display: flex;
|
|
2083
|
+
align-items: baseline;
|
|
2084
|
+
gap: 8px;
|
|
2085
|
+
padding: 2px 0;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
.cm-json-key {
|
|
2089
|
+
color: var(--widget-text-accent, #8cc0ff);
|
|
2090
|
+
min-width: 0;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
.cm-json-meta {
|
|
2094
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.5));
|
|
2095
|
+
font-size: 11px;
|
|
1480
2096
|
}
|
|
1481
2097
|
|
|
1482
|
-
.cm-
|
|
1483
|
-
color:
|
|
1484
|
-
|
|
1485
|
-
|
|
2098
|
+
.cm-json-preview {
|
|
2099
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.7));
|
|
2100
|
+
opacity: 0.9;
|
|
2101
|
+
overflow: hidden;
|
|
2102
|
+
text-overflow: ellipsis;
|
|
2103
|
+
white-space: nowrap;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
.cm-json-children {
|
|
2107
|
+
margin-left: 18px;
|
|
2108
|
+
border-left: 1px dotted color-mix(in srgb, var(--widget-border, rgba(255, 255, 255, 0.2)) 75%, transparent);
|
|
2109
|
+
padding-left: 10px;
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
.cm-json-empty {
|
|
2113
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.45));
|
|
2114
|
+
font-style: italic;
|
|
2115
|
+
padding: 2px 0;
|
|
1486
2116
|
}
|
|
1487
2117
|
|
|
1488
|
-
.cm-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
2118
|
+
.cm-json-value {
|
|
2119
|
+
white-space: pre-wrap;
|
|
2120
|
+
word-break: break-word;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
.cm-json-type-string { color: #ce9178; }
|
|
2124
|
+
.cm-json-type-number { color: #b5cea8; }
|
|
2125
|
+
.cm-json-type-boolean { color: #569cd6; }
|
|
2126
|
+
.cm-json-type-null { color: #808080; }
|
|
2127
|
+
|
|
2128
|
+
.cm-json-fallback {
|
|
2129
|
+
margin: 0;
|
|
2130
|
+
padding: 10px;
|
|
2131
|
+
white-space: pre-wrap;
|
|
2132
|
+
word-break: break-word;
|
|
2133
|
+
color: var(--widget-text-muted, rgba(255, 255, 255, 0.72));
|
|
1493
2134
|
}
|
|
1494
2135
|
|
|
1495
2136
|
/* ANSI text styles */
|
|
@@ -1782,6 +2423,60 @@ ${ansiStyles}
|
|
|
1782
2423
|
.cm-stdin-widget[data-password="true"] .cm-stdin-content {
|
|
1783
2424
|
letter-spacing: 0.2em;
|
|
1784
2425
|
}
|
|
2426
|
+
|
|
2427
|
+
/* ==========================================================================
|
|
2428
|
+
MOBILE RESPONSIVE
|
|
2429
|
+
|
|
2430
|
+
Output blocks need to be readable on narrow screens.
|
|
2431
|
+
Horizontal scroll for wide output, larger text for readability.
|
|
2432
|
+
========================================================================== */
|
|
2433
|
+
|
|
2434
|
+
@media (max-width: 768px) {
|
|
2435
|
+
.cm-output-widget {
|
|
2436
|
+
font-size: max(var(--output-font-size, 0.7em), 12px);
|
|
2437
|
+
left: 0; /* No inset on mobile — use full width */
|
|
2438
|
+
right: 0;
|
|
2439
|
+
padding: 8px 12px;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
/* Output content lines: ensure they don't overflow */
|
|
2443
|
+
.cm-output-content-line {
|
|
2444
|
+
font-size: max(var(--output-font-size, 0.8em), 12px);
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/* Rich output (HTML renders, plots): full width */
|
|
2448
|
+
.cm-output-rich-widget {
|
|
2449
|
+
max-width: 100%;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
.cm-output-rich-widget iframe {
|
|
2453
|
+
max-width: 100%;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
/* Stdin widget: bigger input on mobile */
|
|
2457
|
+
.cm-stdin-widget .cm-stdin-input {
|
|
2458
|
+
font-size: 16px; /* Prevents iOS zoom */
|
|
2459
|
+
padding: 10px;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
.cm-stdin-widget .cm-stdin-prompt {
|
|
2463
|
+
font-size: 14px;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
@media (pointer: coarse) {
|
|
2468
|
+
/* Output container: larger tap target for focus/interaction */
|
|
2469
|
+
.cm-output-widget {
|
|
2470
|
+
padding: 10px 14px;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
/* Collapsed output: easy to tap to expand */
|
|
2474
|
+
.cm-output-collapsed {
|
|
2475
|
+
min-height: 44px;
|
|
2476
|
+
display: flex;
|
|
2477
|
+
align-items: center;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
1785
2480
|
`;
|
|
1786
2481
|
|
|
1787
2482
|
// #endregion STYLES
|