gitorial-cli 2.0.0 → 2.1.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/lib/monaco-assets.js +661 -119
package/package.json
CHANGED
package/src/lib/monaco-assets.js
CHANGED
|
@@ -132,11 +132,109 @@ body {
|
|
|
132
132
|
cursor: pointer;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
.gitorial-monaco-toolbar .copy-toggle {
|
|
136
|
+
background: transparent;
|
|
137
|
+
color: #f0f0f2;
|
|
138
|
+
border: 1px solid #3a3a3f;
|
|
139
|
+
border-radius: 999px;
|
|
140
|
+
padding: 6px 12px;
|
|
141
|
+
font-size: 12px;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.gitorial-monaco-toolbar .retry-toggle {
|
|
146
|
+
background: transparent;
|
|
147
|
+
color: #f0f0f2;
|
|
148
|
+
border: 1px solid #6d737d;
|
|
149
|
+
border-radius: 999px;
|
|
150
|
+
padding: 6px 12px;
|
|
151
|
+
font-size: 12px;
|
|
152
|
+
cursor: pointer;
|
|
153
|
+
display: none;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.gitorial-monaco-toolbar button:disabled {
|
|
157
|
+
opacity: 0.55;
|
|
158
|
+
cursor: not-allowed;
|
|
159
|
+
}
|
|
160
|
+
|
|
135
161
|
.gitorial-monaco-editor {
|
|
162
|
+
position: relative;
|
|
163
|
+
overflow: hidden;
|
|
164
|
+
background: #0f0f10;
|
|
136
165
|
height: 70vh;
|
|
137
166
|
min-height: 520px;
|
|
138
167
|
}
|
|
139
168
|
|
|
169
|
+
.gitorial-monaco-editor .gitorial-monaco-frame {
|
|
170
|
+
width: 100%;
|
|
171
|
+
height: 100%;
|
|
172
|
+
border: 0;
|
|
173
|
+
display: block;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.gitorial-monaco-editor .gitorial-monaco-fallback {
|
|
177
|
+
margin: 0;
|
|
178
|
+
padding: 14px 16px;
|
|
179
|
+
height: 100%;
|
|
180
|
+
overflow: auto;
|
|
181
|
+
background: #0f0f10;
|
|
182
|
+
color: #d6d9df;
|
|
183
|
+
font-family: "JetBrains Mono", "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
184
|
+
font-size: 13px;
|
|
185
|
+
line-height: 1.45;
|
|
186
|
+
white-space: normal;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.gitorial-monaco-editor .gitorial-monaco-fallback.hidden {
|
|
190
|
+
display: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.gitorial-monaco-editor .gitorial-monaco-fallback pre {
|
|
194
|
+
margin: 0;
|
|
195
|
+
white-space: pre;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.gitorial-monaco-editor .gitorial-monaco-fallback code.hljs {
|
|
199
|
+
display: block;
|
|
200
|
+
padding: 0;
|
|
201
|
+
background: transparent;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.gitorial-monaco-editor .gitorial-monaco-fallback .fallback-diff {
|
|
205
|
+
display: grid;
|
|
206
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
207
|
+
gap: 12px;
|
|
208
|
+
height: 100%;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.gitorial-monaco-editor .gitorial-monaco-fallback .fallback-pane {
|
|
212
|
+
border: 1px solid #2a2a2d;
|
|
213
|
+
background: #111214;
|
|
214
|
+
display: flex;
|
|
215
|
+
flex-direction: column;
|
|
216
|
+
min-width: 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.gitorial-monaco-editor .gitorial-monaco-fallback .fallback-pane h4 {
|
|
220
|
+
margin: 0;
|
|
221
|
+
padding: 10px 12px;
|
|
222
|
+
font-size: 12px;
|
|
223
|
+
font-family: "Inter", system-ui, sans-serif;
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
border-bottom: 1px solid #2a2a2d;
|
|
226
|
+
background: #191a1d;
|
|
227
|
+
color: #f0f0f2;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.gitorial-monaco-editor .gitorial-monaco-fallback .fallback-pane pre {
|
|
231
|
+
margin: 0;
|
|
232
|
+
padding: 12px;
|
|
233
|
+
height: 100%;
|
|
234
|
+
overflow: auto;
|
|
235
|
+
white-space: pre;
|
|
236
|
+
}
|
|
237
|
+
|
|
140
238
|
.gitorial-monaco-footer {
|
|
141
239
|
padding: 10px 16px;
|
|
142
240
|
background: #1b1b1d;
|
|
@@ -174,11 +272,18 @@ body {
|
|
|
174
272
|
min-height: 480px;
|
|
175
273
|
height: 60vh;
|
|
176
274
|
}
|
|
275
|
+
|
|
276
|
+
.gitorial-monaco-editor .gitorial-monaco-fallback .fallback-diff {
|
|
277
|
+
grid-template-columns: 1fr;
|
|
278
|
+
}
|
|
177
279
|
}
|
|
178
280
|
`;
|
|
179
281
|
|
|
180
282
|
const monacoSetup = `
|
|
181
283
|
(function () {
|
|
284
|
+
const MONACO_BASE = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.47.0/min/vs';
|
|
285
|
+
const MONACO_TIMEOUT_MS = 3500;
|
|
286
|
+
|
|
182
287
|
function loadJson(url) {
|
|
183
288
|
return fetch(url).then((res) => {
|
|
184
289
|
if (!res.ok) {
|
|
@@ -217,107 +322,402 @@ const monacoSetup = `
|
|
|
217
322
|
}
|
|
218
323
|
}
|
|
219
324
|
|
|
220
|
-
function
|
|
325
|
+
function escapeHtml(value) {
|
|
326
|
+
return String(value)
|
|
327
|
+
.replace(/&/g, '&')
|
|
328
|
+
.replace(/</g, '<')
|
|
329
|
+
.replace(/>/g, '>');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function escapeAttribute(value) {
|
|
333
|
+
return String(value).replace(/"/g, '"');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function mapToHighlightLanguage(language) {
|
|
337
|
+
switch (language) {
|
|
338
|
+
case 'rust':
|
|
339
|
+
return 'rust';
|
|
340
|
+
case 'toml':
|
|
341
|
+
return 'toml';
|
|
342
|
+
case 'javascript':
|
|
343
|
+
return 'javascript';
|
|
344
|
+
case 'json':
|
|
345
|
+
return 'json';
|
|
346
|
+
case 'typescript':
|
|
347
|
+
return 'typescript';
|
|
348
|
+
case 'diff':
|
|
349
|
+
return 'diff';
|
|
350
|
+
case 'markdown':
|
|
351
|
+
return 'markdown';
|
|
352
|
+
default:
|
|
353
|
+
return '';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function runHighlight(root) {
|
|
358
|
+
if (!window.hljs) {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
const codeBlocks = root.querySelectorAll('code[data-gitorial-lang]');
|
|
362
|
+
if (!codeBlocks.length) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
const highlightElement = typeof window.hljs.highlightElement === 'function';
|
|
366
|
+
const highlightBlock = typeof window.hljs.highlightBlock === 'function';
|
|
367
|
+
if (!highlightElement && !highlightBlock) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
codeBlocks.forEach((codeBlock) => {
|
|
371
|
+
try {
|
|
372
|
+
if (highlightElement) {
|
|
373
|
+
window.hljs.highlightElement(codeBlock);
|
|
374
|
+
} else {
|
|
375
|
+
window.hljs.highlightBlock(codeBlock);
|
|
376
|
+
}
|
|
377
|
+
} catch (_error) {
|
|
378
|
+
// Keep plain fallback text if highlighting fails.
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function applyFallbackHighlight(root) {
|
|
385
|
+
if (runHighlight(root)) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
let attempts = 0;
|
|
389
|
+
const maxAttempts = 30;
|
|
390
|
+
const retryMs = 120;
|
|
391
|
+
const timer = setInterval(() => {
|
|
392
|
+
attempts += 1;
|
|
393
|
+
if (runHighlight(root) || attempts >= maxAttempts) {
|
|
394
|
+
clearInterval(timer);
|
|
395
|
+
}
|
|
396
|
+
}, retryMs);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function renderFallback(editorNode, payload) {
|
|
400
|
+
let fallback = editorNode.querySelector('.gitorial-monaco-fallback');
|
|
401
|
+
if (!fallback) {
|
|
402
|
+
fallback = document.createElement('div');
|
|
403
|
+
fallback.className = 'gitorial-monaco-fallback';
|
|
404
|
+
editorNode.appendChild(fallback);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const highlightLanguage = mapToHighlightLanguage(payload.language || '');
|
|
408
|
+
const codeClass = highlightLanguage ? 'language-' + highlightLanguage : 'language-plaintext';
|
|
409
|
+
const codeMeta = 'data-gitorial-lang="' + escapeAttribute(highlightLanguage || 'plaintext') + '"';
|
|
410
|
+
|
|
411
|
+
if (payload.type === 'diff') {
|
|
412
|
+
fallback.innerHTML =
|
|
413
|
+
'<div class="fallback-diff">' +
|
|
414
|
+
'<section class="fallback-pane">' +
|
|
415
|
+
'<h4>Template</h4>' +
|
|
416
|
+
'<pre><code class="' +
|
|
417
|
+
codeClass +
|
|
418
|
+
'" ' +
|
|
419
|
+
codeMeta +
|
|
420
|
+
'>' +
|
|
421
|
+
escapeHtml(payload.original || '') +
|
|
422
|
+
'</code></pre>' +
|
|
423
|
+
'</section>' +
|
|
424
|
+
'<section class="fallback-pane">' +
|
|
425
|
+
'<h4>Solution</h4>' +
|
|
426
|
+
'<pre><code class="' +
|
|
427
|
+
codeClass +
|
|
428
|
+
'" ' +
|
|
429
|
+
codeMeta +
|
|
430
|
+
'>' +
|
|
431
|
+
escapeHtml(payload.modified || '') +
|
|
432
|
+
'</code></pre>' +
|
|
433
|
+
'</section>' +
|
|
434
|
+
'</div>';
|
|
435
|
+
applyFallbackHighlight(fallback);
|
|
436
|
+
fallback.classList.remove('hidden');
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
fallback.innerHTML =
|
|
441
|
+
'<pre><code class="' +
|
|
442
|
+
codeClass +
|
|
443
|
+
'" ' +
|
|
444
|
+
codeMeta +
|
|
445
|
+
'>' +
|
|
446
|
+
escapeHtml(payload.content || '') +
|
|
447
|
+
'</code></pre>';
|
|
448
|
+
applyFallbackHighlight(fallback);
|
|
449
|
+
fallback.classList.remove('hidden');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function createMonacoSession(editorNode, onReady, onFailure, onEdit) {
|
|
221
453
|
const iframe = document.createElement('iframe');
|
|
222
454
|
iframe.setAttribute('title', 'Gitorial Editor');
|
|
223
|
-
iframe.
|
|
224
|
-
iframe.style.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
455
|
+
iframe.className = 'gitorial-monaco-frame';
|
|
456
|
+
iframe.style.display = 'none';
|
|
457
|
+
|
|
458
|
+
const iframeDocument = [
|
|
459
|
+
'<!doctype html>',
|
|
460
|
+
'<html>',
|
|
461
|
+
'<head>',
|
|
462
|
+
'<meta charset="utf-8" />',
|
|
463
|
+
'<style>html, body { margin: 0; height: 100%; background: #0f0f10; } #editor { height: 100%; } .monaco-editor .gitorial-todo-line { background: rgba(255, 196, 0, 0.12); } .monaco-editor .margin .gitorial-todo-glyph { border-left: 3px solid #ffc400; box-sizing: border-box; }</style>',
|
|
464
|
+
'</head>',
|
|
465
|
+
'<body>',
|
|
466
|
+
'<div id="editor"></div>',
|
|
467
|
+
'<script>',
|
|
468
|
+
'(function () {',
|
|
469
|
+
" var MONACO_BASE = '" + MONACO_BASE + "';",
|
|
470
|
+
' var editor = null;',
|
|
471
|
+
' var diffEditor = null;',
|
|
472
|
+
' var changeSubscription = null;',
|
|
473
|
+
' var todoDecorations = [];',
|
|
474
|
+
' var highlightTodos = false;',
|
|
475
|
+
" var currentMode = 'single';",
|
|
476
|
+
' var pendingPayload = null;',
|
|
477
|
+
' var isReady = false;',
|
|
478
|
+
' var suppressChange = false;',
|
|
479
|
+
' function post(type, detail) {',
|
|
480
|
+
" parent.postMessage({ source: 'gitorial-monaco', type: type, detail: detail }, '*');",
|
|
481
|
+
' }',
|
|
482
|
+
' function getContainer() { return document.getElementById(\\'editor\\'); }',
|
|
483
|
+
' function resetContainer() {',
|
|
484
|
+
' var container = getContainer();',
|
|
485
|
+
' container.innerHTML = "";',
|
|
486
|
+
' }',
|
|
487
|
+
' function attachChangeHandler() {',
|
|
488
|
+
' if (!editor) { return; }',
|
|
489
|
+
' if (changeSubscription) {',
|
|
490
|
+
' changeSubscription.dispose();',
|
|
491
|
+
' changeSubscription = null;',
|
|
492
|
+
' }',
|
|
493
|
+
' changeSubscription = editor.onDidChangeModelContent(function () {',
|
|
494
|
+
' if (suppressChange) { return; }',
|
|
495
|
+
' syncTodoDecorations();',
|
|
496
|
+
" post('change', editor.getValue());",
|
|
497
|
+
' });',
|
|
498
|
+
' }',
|
|
499
|
+
' function clearTodoDecorations() {',
|
|
500
|
+
' if (!editor) { return; }',
|
|
501
|
+
' todoDecorations = editor.deltaDecorations(todoDecorations, []);',
|
|
502
|
+
' }',
|
|
503
|
+
' function syncTodoDecorations() {',
|
|
504
|
+
' if (!editor) { return; }',
|
|
505
|
+
' if (!highlightTodos) {',
|
|
506
|
+
' clearTodoDecorations();',
|
|
507
|
+
' return;',
|
|
508
|
+
' }',
|
|
509
|
+
' var model = editor.getModel();',
|
|
510
|
+
' if (!model) {',
|
|
511
|
+
' clearTodoDecorations();',
|
|
512
|
+
' return;',
|
|
513
|
+
' }',
|
|
514
|
+
' var matches = model.findMatches("TODO", true, false, false, null, true);',
|
|
515
|
+
' var next = matches.map(function (match) {',
|
|
516
|
+
' return {',
|
|
517
|
+
' range: match.range,',
|
|
518
|
+
' options: {',
|
|
519
|
+
' isWholeLine: true,',
|
|
520
|
+
' className: "gitorial-todo-line",',
|
|
521
|
+
' linesDecorationsClassName: "gitorial-todo-glyph"',
|
|
522
|
+
' }',
|
|
523
|
+
' };',
|
|
524
|
+
' });',
|
|
525
|
+
' todoDecorations = editor.deltaDecorations(todoDecorations, next);',
|
|
526
|
+
' }',
|
|
527
|
+
' function disposeEditorModel() {',
|
|
528
|
+
' if (!editor) { return; }',
|
|
529
|
+
' var model = editor.getModel();',
|
|
530
|
+
' if (model) { model.dispose(); }',
|
|
531
|
+
' }',
|
|
532
|
+
' function disposeDiffModels() {',
|
|
533
|
+
' if (!diffEditor) { return; }',
|
|
534
|
+
' var models = diffEditor.getModel();',
|
|
535
|
+
' if (!models) { return; }',
|
|
536
|
+
' if (models.original) { models.original.dispose(); }',
|
|
537
|
+
' if (models.modified) { models.modified.dispose(); }',
|
|
538
|
+
' }',
|
|
539
|
+
' function disposeEditor() {',
|
|
540
|
+
' if (!editor) { return; }',
|
|
541
|
+
' if (changeSubscription) {',
|
|
542
|
+
' changeSubscription.dispose();',
|
|
543
|
+
' changeSubscription = null;',
|
|
544
|
+
' }',
|
|
545
|
+
' disposeEditorModel();',
|
|
546
|
+
' todoDecorations = [];',
|
|
547
|
+
' editor.dispose();',
|
|
548
|
+
' editor = null;',
|
|
549
|
+
' }',
|
|
550
|
+
' function disposeDiffEditor() {',
|
|
551
|
+
' if (!diffEditor) { return; }',
|
|
552
|
+
' disposeDiffModels();',
|
|
553
|
+
' diffEditor.dispose();',
|
|
554
|
+
' diffEditor = null;',
|
|
555
|
+
' }',
|
|
556
|
+
' function ensureEditor(content, language, readOnly) {',
|
|
557
|
+
" if (currentMode !== 'single') {",
|
|
558
|
+
' disposeDiffEditor();',
|
|
559
|
+
' resetContainer();',
|
|
560
|
+
" currentMode = 'single';",
|
|
561
|
+
' }',
|
|
562
|
+
' if (!editor) {',
|
|
563
|
+
' editor = monaco.editor.create(getContainer(), {',
|
|
564
|
+
' value: content,',
|
|
565
|
+
' language: language,',
|
|
566
|
+
" theme: 'vs-dark',",
|
|
567
|
+
' automaticLayout: true,',
|
|
568
|
+
' readOnly: !!readOnly',
|
|
569
|
+
' });',
|
|
570
|
+
' highlightTodos = !readOnly;',
|
|
571
|
+
' syncTodoDecorations();',
|
|
572
|
+
' attachChangeHandler();',
|
|
573
|
+
' return;',
|
|
574
|
+
' }',
|
|
575
|
+
' editor.updateOptions({ readOnly: !!readOnly });',
|
|
576
|
+
' highlightTodos = !readOnly;',
|
|
577
|
+
' suppressChange = true;',
|
|
578
|
+
' disposeEditorModel();',
|
|
579
|
+
' editor.setModel(monaco.editor.createModel(content, language));',
|
|
580
|
+
' suppressChange = false;',
|
|
581
|
+
' syncTodoDecorations();',
|
|
582
|
+
' }',
|
|
583
|
+
' function ensureDiffEditor(original, modified, language) {',
|
|
584
|
+
" if (currentMode !== 'diff') {",
|
|
585
|
+
' disposeEditor();',
|
|
586
|
+
' resetContainer();',
|
|
587
|
+
" currentMode = 'diff';",
|
|
588
|
+
' }',
|
|
589
|
+
' if (!diffEditor) {',
|
|
590
|
+
' diffEditor = monaco.editor.createDiffEditor(getContainer(), {',
|
|
591
|
+
" theme: 'vs-dark',",
|
|
592
|
+
' automaticLayout: true,',
|
|
593
|
+
' readOnly: true,',
|
|
594
|
+
' renderSideBySide: true',
|
|
595
|
+
' });',
|
|
596
|
+
' }',
|
|
597
|
+
' disposeDiffModels();',
|
|
598
|
+
' var originalModel = monaco.editor.createModel(original, language);',
|
|
599
|
+
' var modifiedModel = monaco.editor.createModel(modified, language);',
|
|
600
|
+
' diffEditor.setModel({ original: originalModel, modified: modifiedModel });',
|
|
601
|
+
' }',
|
|
602
|
+
' function renderPayload(payload) {',
|
|
603
|
+
" if (payload.type === 'diff') {",
|
|
604
|
+
" ensureDiffEditor(payload.original || '', payload.modified || '', payload.language || 'plaintext');",
|
|
605
|
+
' return;',
|
|
606
|
+
' }',
|
|
607
|
+
" ensureEditor(payload.content || '', payload.language || 'plaintext', !!payload.readOnly);",
|
|
608
|
+
' }',
|
|
609
|
+
' window.addEventListener(\\'message\\', function (event) {',
|
|
610
|
+
' var data = event.data || {};',
|
|
611
|
+
" if (data.source !== 'gitorial-monaco-host' || !data.type) { return; }",
|
|
612
|
+
' if (!isReady) {',
|
|
613
|
+
' pendingPayload = data;',
|
|
614
|
+
' return;',
|
|
615
|
+
' }',
|
|
616
|
+
' renderPayload(data);',
|
|
617
|
+
' });',
|
|
618
|
+
' function bootMonaco() {',
|
|
619
|
+
" if (typeof window.require !== 'function') {",
|
|
620
|
+
" post('error', 'require_missing');",
|
|
621
|
+
' return;',
|
|
622
|
+
' }',
|
|
623
|
+
' window.require.config({ paths: { vs: MONACO_BASE } });',
|
|
624
|
+
" window.require(['vs/editor/editor.main'], function () {",
|
|
625
|
+
' isReady = true;',
|
|
626
|
+
" post('ready');",
|
|
627
|
+
' if (pendingPayload) {',
|
|
628
|
+
' renderPayload(pendingPayload);',
|
|
629
|
+
' pendingPayload = null;',
|
|
630
|
+
' }',
|
|
631
|
+
' }, function (err) {',
|
|
632
|
+
" post('error', String(err || 'require_failed'));",
|
|
633
|
+
' });',
|
|
634
|
+
' }',
|
|
635
|
+
" var loader = document.createElement('script');",
|
|
636
|
+
" loader.src = MONACO_BASE + '/loader.js';",
|
|
637
|
+
' loader.async = true;',
|
|
638
|
+
" loader.onload = bootMonaco;",
|
|
639
|
+
" loader.onerror = function () { post('error', 'loader_failed'); };",
|
|
640
|
+
' document.head.appendChild(loader);',
|
|
641
|
+
'})();',
|
|
642
|
+
'</script>',
|
|
643
|
+
'</body>',
|
|
644
|
+
'</html>',
|
|
645
|
+
].join('');
|
|
646
|
+
|
|
647
|
+
let settled = false;
|
|
648
|
+
let timeoutId = null;
|
|
649
|
+
|
|
650
|
+
function clearTimer() {
|
|
651
|
+
if (timeoutId) {
|
|
652
|
+
clearTimeout(timeoutId);
|
|
653
|
+
timeoutId = null;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function cleanUp() {
|
|
658
|
+
clearTimer();
|
|
659
|
+
window.removeEventListener('message', onMessage);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function onMessage(event) {
|
|
663
|
+
if (event.source !== iframe.contentWindow) {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const data = event.data || {};
|
|
667
|
+
if (data.source !== 'gitorial-monaco') {
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (data.type === 'change') {
|
|
671
|
+
onEdit(data.detail || '');
|
|
672
|
+
return;
|
|
258
673
|
}
|
|
259
|
-
|
|
260
|
-
if (
|
|
261
|
-
|
|
262
|
-
diffEditor = null;
|
|
674
|
+
if (data.type === 'ready') {
|
|
675
|
+
if (settled) {
|
|
676
|
+
return;
|
|
263
677
|
}
|
|
678
|
+
settled = true;
|
|
679
|
+
clearTimer();
|
|
680
|
+
onReady(iframe);
|
|
681
|
+
return;
|
|
264
682
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (!editor) {
|
|
273
|
-
editor = monaco.editor.create(getContainer(), {
|
|
274
|
-
value: content,
|
|
275
|
-
language: language,
|
|
276
|
-
theme: 'vs-dark',
|
|
277
|
-
automaticLayout: true,
|
|
278
|
-
readOnly: true,
|
|
279
|
-
});
|
|
280
|
-
} else {
|
|
281
|
-
const model = monaco.editor.createModel(content, language);
|
|
282
|
-
editor.setModel(model);
|
|
283
|
-
}
|
|
284
|
-
});
|
|
683
|
+
if (data.type === 'error') {
|
|
684
|
+
if (settled) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
settled = true;
|
|
688
|
+
cleanUp();
|
|
689
|
+
onFailure(data.detail || 'load_error');
|
|
285
690
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
currentMode = 'diff';
|
|
292
|
-
}
|
|
293
|
-
if (!diffEditor) {
|
|
294
|
-
diffEditor = monaco.editor.createDiffEditor(getContainer(), {
|
|
295
|
-
theme: 'vs-dark',
|
|
296
|
-
automaticLayout: true,
|
|
297
|
-
readOnly: true,
|
|
298
|
-
renderSideBySide: true,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
const originalModel = monaco.editor.createModel(original, language);
|
|
302
|
-
const modifiedModel = monaco.editor.createModel(modified, language);
|
|
303
|
-
diffEditor.setModel({ original: originalModel, modified: modifiedModel });
|
|
304
|
-
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
timeoutId = setTimeout(() => {
|
|
694
|
+
if (settled) {
|
|
695
|
+
return;
|
|
305
696
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
697
|
+
settled = true;
|
|
698
|
+
cleanUp();
|
|
699
|
+
onFailure('timeout');
|
|
700
|
+
}, MONACO_TIMEOUT_MS);
|
|
701
|
+
|
|
702
|
+
window.addEventListener('message', onMessage);
|
|
703
|
+
editorNode.appendChild(iframe);
|
|
704
|
+
iframe.srcdoc = iframeDocument;
|
|
705
|
+
|
|
706
|
+
return {
|
|
707
|
+
iframe,
|
|
708
|
+
post(payload) {
|
|
709
|
+
if (!iframe.contentWindow) {
|
|
309
710
|
return;
|
|
310
711
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
712
|
+
iframe.contentWindow.postMessage({ source: 'gitorial-monaco-host', ...payload }, '*');
|
|
713
|
+
},
|
|
714
|
+
destroy() {
|
|
715
|
+
cleanUp();
|
|
716
|
+
if (iframe.parentNode) {
|
|
717
|
+
iframe.parentNode.removeChild(iframe);
|
|
315
718
|
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
</body>
|
|
319
|
-
</html>\`;
|
|
320
|
-
return iframe;
|
|
719
|
+
},
|
|
720
|
+
};
|
|
321
721
|
}
|
|
322
722
|
|
|
323
723
|
async function initMonaco(container) {
|
|
@@ -328,26 +728,27 @@ const monacoSetup = `
|
|
|
328
728
|
const select = container.querySelector('[data-gitorial-files]');
|
|
329
729
|
const toggle = container.querySelector('[data-gitorial-toggle]');
|
|
330
730
|
const diffToggle = container.querySelector('[data-gitorial-diff]');
|
|
731
|
+
const copyToggle = container.querySelector('[data-gitorial-copy]');
|
|
331
732
|
const footer = container.querySelector('[data-gitorial-footer]');
|
|
332
733
|
const editorNode = container.querySelector('[data-gitorial-editor]');
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
iframeReady = true;
|
|
340
|
-
if (pendingPayload) {
|
|
341
|
-
iframe.contentWindow.postMessage(pendingPayload, '*');
|
|
342
|
-
pendingPayload = null;
|
|
343
|
-
}
|
|
344
|
-
});
|
|
734
|
+
const retryToggle = document.createElement('button');
|
|
735
|
+
retryToggle.type = 'button';
|
|
736
|
+
retryToggle.className = 'retry-toggle';
|
|
737
|
+
retryToggle.textContent = 'Retry editor';
|
|
738
|
+
toolbar.appendChild(retryToggle);
|
|
739
|
+
copyToggle.disabled = true;
|
|
345
740
|
|
|
346
741
|
let currentMode = 'template';
|
|
347
742
|
let previousMode = 'template';
|
|
348
743
|
let selectedLabel = null;
|
|
349
|
-
|
|
350
|
-
|
|
744
|
+
const templateFiles = config.template || [];
|
|
745
|
+
const solutionFiles = config.solution || [];
|
|
746
|
+
const fileContentCache = new Map();
|
|
747
|
+
const templateEdits = new Map();
|
|
748
|
+
let monacoSession = null;
|
|
749
|
+
let monacoReady = false;
|
|
750
|
+
let lastPayload = null;
|
|
751
|
+
let copyState = { text: '', label: '' };
|
|
351
752
|
|
|
352
753
|
if (!solutionFiles.length) {
|
|
353
754
|
toggle.style.display = 'none';
|
|
@@ -407,19 +808,114 @@ const monacoSetup = `
|
|
|
407
808
|
}
|
|
408
809
|
|
|
409
810
|
function getFileContent(filePath) {
|
|
410
|
-
|
|
811
|
+
if (fileContentCache.has(filePath)) {
|
|
812
|
+
return Promise.resolve(fileContentCache.get(filePath));
|
|
813
|
+
}
|
|
814
|
+
return fetch(filePath)
|
|
815
|
+
.then((res) => res.text())
|
|
816
|
+
.then((text) => {
|
|
817
|
+
fileContentCache.set(filePath, text);
|
|
818
|
+
return text;
|
|
819
|
+
});
|
|
411
820
|
}
|
|
412
821
|
|
|
413
822
|
function findFile(list, label) {
|
|
414
823
|
return list.find((file) => file.label === label) || null;
|
|
415
824
|
}
|
|
416
825
|
|
|
417
|
-
async function
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
826
|
+
async function copyTextToClipboard(text) {
|
|
827
|
+
if (!text) {
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
831
|
+
try {
|
|
832
|
+
await navigator.clipboard.writeText(text);
|
|
833
|
+
return true;
|
|
834
|
+
} catch (_error) {}
|
|
835
|
+
}
|
|
836
|
+
const textarea = document.createElement('textarea');
|
|
837
|
+
textarea.value = text;
|
|
838
|
+
textarea.setAttribute('readonly', 'readonly');
|
|
839
|
+
textarea.style.position = 'absolute';
|
|
840
|
+
textarea.style.left = '-9999px';
|
|
841
|
+
document.body.appendChild(textarea);
|
|
842
|
+
textarea.select();
|
|
843
|
+
let copied = false;
|
|
844
|
+
try {
|
|
845
|
+
copied = document.execCommand('copy');
|
|
846
|
+
} catch (_error) {
|
|
847
|
+
copied = false;
|
|
848
|
+
}
|
|
849
|
+
document.body.removeChild(textarea);
|
|
850
|
+
return copied;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
function setCopyState(text, label) {
|
|
854
|
+
copyState = { text: text || '', label: label || '' };
|
|
855
|
+
copyToggle.disabled = !copyState.text;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function flashCopyLabel(label) {
|
|
859
|
+
copyToggle.textContent = label;
|
|
860
|
+
setTimeout(() => {
|
|
861
|
+
copyToggle.textContent = 'Copy code';
|
|
862
|
+
}, 1300);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function setRetryVisible(visible) {
|
|
866
|
+
retryToggle.style.display = visible ? 'inline-block' : 'none';
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function startMonacoSession() {
|
|
870
|
+
if (monacoSession) {
|
|
871
|
+
monacoSession.destroy();
|
|
872
|
+
monacoSession = null;
|
|
873
|
+
}
|
|
874
|
+
monacoReady = false;
|
|
875
|
+
setRetryVisible(false);
|
|
876
|
+
monacoSession = createMonacoSession(
|
|
877
|
+
editorNode,
|
|
878
|
+
(iframe) => {
|
|
879
|
+
monacoReady = true;
|
|
880
|
+
iframe.style.display = 'block';
|
|
881
|
+
const fallback = editorNode.querySelector('.gitorial-monaco-fallback');
|
|
882
|
+
if (fallback) {
|
|
883
|
+
fallback.classList.add('hidden');
|
|
884
|
+
}
|
|
885
|
+
if (lastPayload) {
|
|
886
|
+
monacoSession.post(lastPayload);
|
|
887
|
+
}
|
|
888
|
+
},
|
|
889
|
+
(reason) => {
|
|
890
|
+
monacoReady = false;
|
|
891
|
+
if (monacoSession) {
|
|
892
|
+
monacoSession.destroy();
|
|
893
|
+
monacoSession = null;
|
|
894
|
+
}
|
|
895
|
+
setRetryVisible(true);
|
|
896
|
+
footer.textContent = 'Monaco is unavailable (' + reason + '). Using fallback renderer.';
|
|
897
|
+
},
|
|
898
|
+
(content) => {
|
|
899
|
+
if (currentMode !== 'template' || !selectedLabel) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
templateEdits.set(selectedLabel, content);
|
|
903
|
+
setCopyState(content, selectedLabel + ' (template)');
|
|
904
|
+
}
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function renderPayload(payload) {
|
|
909
|
+
lastPayload = payload;
|
|
910
|
+
if (monacoReady && monacoSession) {
|
|
911
|
+
const fallback = editorNode.querySelector('.gitorial-monaco-fallback');
|
|
912
|
+
if (fallback) {
|
|
913
|
+
fallback.classList.add('hidden');
|
|
914
|
+
}
|
|
915
|
+
monacoSession.post(payload);
|
|
916
|
+
return;
|
|
422
917
|
}
|
|
918
|
+
renderFallback(editorNode, payload);
|
|
423
919
|
}
|
|
424
920
|
|
|
425
921
|
async function setEditorFile(label) {
|
|
@@ -429,10 +925,20 @@ const monacoSetup = `
|
|
|
429
925
|
if (currentMode === 'diff') {
|
|
430
926
|
const templateFile = findFile(templateFiles, label);
|
|
431
927
|
const solutionFile = findFile(solutionFiles, label);
|
|
432
|
-
|
|
928
|
+
let original = '';
|
|
929
|
+
if (templateEdits.has(label)) {
|
|
930
|
+
original = templateEdits.get(label);
|
|
931
|
+
} else if (templateFile) {
|
|
932
|
+
original = await getFileContent(templateFile.path);
|
|
933
|
+
}
|
|
433
934
|
const modified = solutionFile ? await getFileContent(solutionFile.path) : '';
|
|
434
935
|
const language = detectMode((solutionFile || templateFile || { path: '' }).path);
|
|
435
|
-
|
|
936
|
+
renderPayload({ type: 'diff', original, modified, language });
|
|
937
|
+
if (solutionFile) {
|
|
938
|
+
setCopyState(modified, label + ' (solution)');
|
|
939
|
+
} else {
|
|
940
|
+
setCopyState(original, label + ' (template)');
|
|
941
|
+
}
|
|
436
942
|
return;
|
|
437
943
|
}
|
|
438
944
|
|
|
@@ -441,13 +947,15 @@ const monacoSetup = `
|
|
|
441
947
|
if (!file) {
|
|
442
948
|
return;
|
|
443
949
|
}
|
|
444
|
-
|
|
950
|
+
let content = '';
|
|
951
|
+
if (currentMode === 'template' && templateEdits.has(label)) {
|
|
952
|
+
content = templateEdits.get(label);
|
|
953
|
+
} else {
|
|
954
|
+
content = await getFileContent(file.path);
|
|
955
|
+
}
|
|
445
956
|
const language = detectMode(file.path);
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
function currentFiles() {
|
|
450
|
-
return currentMode === 'template' ? templateFiles : solutionFiles;
|
|
957
|
+
renderPayload({ type: 'set', content, language, readOnly: currentMode !== 'template' });
|
|
958
|
+
setCopyState(content, label + ' (' + currentMode + ')');
|
|
451
959
|
}
|
|
452
960
|
|
|
453
961
|
updateFileOptions();
|
|
@@ -458,6 +966,7 @@ const monacoSetup = `
|
|
|
458
966
|
}
|
|
459
967
|
selectedLabel = select.value;
|
|
460
968
|
await setEditorFile(selectedLabel);
|
|
969
|
+
startMonacoSession();
|
|
461
970
|
|
|
462
971
|
select.addEventListener('change', async () => {
|
|
463
972
|
selectedLabel = select.value;
|
|
@@ -497,11 +1006,38 @@ const monacoSetup = `
|
|
|
497
1006
|
}
|
|
498
1007
|
updateFileOptions();
|
|
499
1008
|
toggle.disabled = currentMode === 'diff';
|
|
1009
|
+
if (monacoSession) {
|
|
1010
|
+
startMonacoSession();
|
|
1011
|
+
}
|
|
500
1012
|
if (select.value) {
|
|
501
1013
|
selectedLabel = select.value;
|
|
502
1014
|
await setEditorFile(selectedLabel);
|
|
503
1015
|
}
|
|
504
1016
|
});
|
|
1017
|
+
|
|
1018
|
+
copyToggle.addEventListener('click', async () => {
|
|
1019
|
+
if (!copyState.text) {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const ok = await copyTextToClipboard(copyState.text);
|
|
1023
|
+
if (ok) {
|
|
1024
|
+
flashCopyLabel('Copied');
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
footer.textContent = 'Copy failed for ' + copyState.label + '.';
|
|
1028
|
+
flashCopyLabel('Copy failed');
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
retryToggle.addEventListener('click', () => {
|
|
1032
|
+
startMonacoSession();
|
|
1033
|
+
if (!solutionFiles.length) {
|
|
1034
|
+
footer.textContent = 'Template view only.';
|
|
1035
|
+
} else if (currentMode === 'template') {
|
|
1036
|
+
footer.textContent = 'Template view. Click View solution to compare.';
|
|
1037
|
+
} else {
|
|
1038
|
+
footer.textContent = 'Solution view. Click Back to template to continue.';
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
505
1041
|
}
|
|
506
1042
|
|
|
507
1043
|
function boot() {
|
|
@@ -510,8 +1046,13 @@ const monacoSetup = `
|
|
|
510
1046
|
return;
|
|
511
1047
|
}
|
|
512
1048
|
containers.forEach((container) => {
|
|
1049
|
+
if (container.dataset.gitorialInitialized === 'true') {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
container.dataset.gitorialInitialized = 'true';
|
|
513
1053
|
initMonaco(container).catch((error) => {
|
|
514
1054
|
console.error(error);
|
|
1055
|
+
delete container.dataset.gitorialInitialized;
|
|
515
1056
|
});
|
|
516
1057
|
});
|
|
517
1058
|
}
|
|
@@ -530,6 +1071,7 @@ const monacoEmbed = (relativeAssetBase, manifestPath) => `
|
|
|
530
1071
|
<select class="file-select" data-gitorial-files></select>
|
|
531
1072
|
<button class="toggle" data-gitorial-toggle>View solution</button>
|
|
532
1073
|
<button class="diff-toggle" data-gitorial-diff>View diff</button>
|
|
1074
|
+
<button class="copy-toggle" data-gitorial-copy>Copy code</button>
|
|
533
1075
|
</div>
|
|
534
1076
|
<div class="gitorial-monaco-editor" data-gitorial-editor></div>
|
|
535
1077
|
<div class="gitorial-monaco-footer" data-gitorial-footer></div>
|