@metricinsights/pp-dev 1.5.0-beta.34 → 1.5.0-beta.36
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/README.md +17 -9
- package/dist/cjs/auth.provider-D5AI-ujG.js +2 -0
- package/dist/cjs/auth.provider-D5AI-ujG.js.map +1 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/plugin.js +1 -1
- package/dist/cjs/plugin.js.map +1 -1
- package/dist/cjs/{version-manifest-DdWlzzZ6.js → version-manifest-DuXhzqGr.js} +2 -2
- package/dist/cjs/{version-manifest-DdWlzzZ6.js.map → version-manifest-DuXhzqGr.js.map} +1 -1
- package/dist/cjs/version-plugin-DrG2-mQf.js +2 -0
- package/dist/cjs/{version-plugin-B1Sb07cW.js.map → version-plugin-DrG2-mQf.js.map} +1 -1
- package/dist/client/client.js +113 -53
- package/dist/client/client.js.map +1 -1
- package/dist/client/request-inspector.js +12 -1
- package/dist/client/request-inspector.js.map +1 -1
- package/dist/client/variables-editor.js +169 -36
- package/dist/client/variables-editor.js.map +1 -1
- package/dist/esm/cli.js +1 -1
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/{internal.middleware-CC20He9L.js → internal.middleware-wi0Mb4oZ.js} +2 -2
- package/dist/esm/internal.middleware-wi0Mb4oZ.js.map +1 -0
- package/dist/esm/plugin.js +1 -1
- package/package.json +6 -8
- package/dist/cjs/auth.provider-6rZUaR33.js +0 -2
- package/dist/cjs/auth.provider-6rZUaR33.js.map +0 -1
- package/dist/cjs/cli.js +0 -2
- package/dist/cjs/cli.js.map +0 -1
- package/dist/cjs/index-Cc-bAhnE.js +0 -2
- package/dist/cjs/index-Cc-bAhnE.js.map +0 -1
- package/dist/cjs/internal.middleware-BlM7gqkq.js +0 -2
- package/dist/cjs/internal.middleware-BlM7gqkq.js.map +0 -1
- package/dist/cjs/migrate-DDdeB-Fj.js +0 -2
- package/dist/cjs/migrate-DDdeB-Fj.js.map +0 -1
- package/dist/cjs/version-plugin-B1Sb07cW.js +0 -2
- package/dist/esm/internal.middleware-CC20He9L.js.map +0 -1
|
@@ -364,6 +364,70 @@ const find = (root, selector) => root.querySelector(selector);
|
|
|
364
364
|
// Enter on a focused button is that button's own click (Cancel, ✕, the diff's ←/→), not the
|
|
365
365
|
// dialog's confirmation: as the latter, it saved or discarded on a Cancel reached by Shift+Tab.
|
|
366
366
|
const isButton = (target) => target instanceof HTMLButtonElement;
|
|
367
|
+
const FOCUSABLE = 'button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])';
|
|
368
|
+
/**
|
|
369
|
+
* Listens for `overlay`'s keys on the document while it is the dialog on top, and returns the
|
|
370
|
+
* function that stops listening (also stopped once the overlay leaves the page).
|
|
371
|
+
*
|
|
372
|
+
* On the overlay itself, the keys reached a dialog only while the focus was inside it: a click on
|
|
373
|
+
* the diff's text or the dialog's body put the focus on `<body>`, and Escape (or ←/→) did nothing.
|
|
374
|
+
* Tab cycles within the dialog: it left it for the page behind (the toolbar's Save).
|
|
375
|
+
*/
|
|
376
|
+
function bindModalKeys(overlay, onKey) {
|
|
377
|
+
const listener = (ev) => {
|
|
378
|
+
if (!overlay.isConnected) {
|
|
379
|
+
document.removeEventListener('keydown', listener);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const overlays = document.querySelectorAll('.ve-modal-overlay');
|
|
383
|
+
if (overlays[overlays.length - 1] !== overlay) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (ev.key === 'Tab') {
|
|
387
|
+
trapTab(overlay, ev);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
onKey(ev);
|
|
391
|
+
};
|
|
392
|
+
document.addEventListener('keydown', listener);
|
|
393
|
+
return () => document.removeEventListener('keydown', listener);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Whether `el` can take the focus: not hidden, itself or by an element between it and `overlay`
|
|
397
|
+
* (the import picker hides the pages its filter leaves out with `display: none`). A hidden last
|
|
398
|
+
* control let Tab leave the dialog, and Shift+Tab tried to focus it.
|
|
399
|
+
*/
|
|
400
|
+
function isTabbable(el, overlay) {
|
|
401
|
+
for (let node = el; node && node !== overlay; node = node.parentElement) {
|
|
402
|
+
if (node.hidden) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
const style = getComputedStyle(node);
|
|
406
|
+
if (style.display === 'none' || style.visibility === 'hidden') {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
function trapTab(overlay, ev) {
|
|
413
|
+
const focusable = Array.from(overlay.querySelectorAll(FOCUSABLE)).filter((el) => isTabbable(el, overlay));
|
|
414
|
+
if (focusable.length === 0) {
|
|
415
|
+
ev.preventDefault();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const first = focusable[0];
|
|
419
|
+
const last = focusable[focusable.length - 1];
|
|
420
|
+
const active = document.activeElement;
|
|
421
|
+
const inside = active instanceof Node && overlay.contains(active);
|
|
422
|
+
if (ev.shiftKey && (!inside || active === first)) {
|
|
423
|
+
ev.preventDefault();
|
|
424
|
+
last.focus();
|
|
425
|
+
}
|
|
426
|
+
else if (!ev.shiftKey && (!inside || active === last)) {
|
|
427
|
+
ev.preventDefault();
|
|
428
|
+
first.focus();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
367
431
|
// Asks for a value (in place of window.prompt()); `onDone` gets `null` when the user cancels.
|
|
368
432
|
function showCustomValueModal(title, initialValue, onDone) {
|
|
369
433
|
const overlay = openOverlay('<div class="ve-modal">' +
|
|
@@ -382,6 +446,7 @@ function showCustomValueModal(title, initialValue, onDone) {
|
|
|
382
446
|
inputEl.focus();
|
|
383
447
|
inputEl.select();
|
|
384
448
|
function close(value) {
|
|
449
|
+
unbindKeys();
|
|
385
450
|
overlay.remove();
|
|
386
451
|
onDone(value);
|
|
387
452
|
}
|
|
@@ -392,8 +457,8 @@ function showCustomValueModal(title, initialValue, onDone) {
|
|
|
392
457
|
close(null);
|
|
393
458
|
}
|
|
394
459
|
});
|
|
395
|
-
|
|
396
|
-
if (ev.key === 'Enter') {
|
|
460
|
+
const unbindKeys = bindModalKeys(overlay, (ev) => {
|
|
461
|
+
if (ev.key === 'Enter' && !isButton(ev.target)) {
|
|
397
462
|
ev.preventDefault();
|
|
398
463
|
close(inputEl.value);
|
|
399
464
|
}
|
|
@@ -417,6 +482,7 @@ function showConfirmModal(message, onConfirm) {
|
|
|
417
482
|
'</div>');
|
|
418
483
|
find(overlay, '.ve-modal-ok').focus();
|
|
419
484
|
function close(confirmed) {
|
|
485
|
+
unbindKeys();
|
|
420
486
|
overlay.remove();
|
|
421
487
|
if (confirmed) {
|
|
422
488
|
onConfirm();
|
|
@@ -429,7 +495,7 @@ function showConfirmModal(message, onConfirm) {
|
|
|
429
495
|
close(false);
|
|
430
496
|
}
|
|
431
497
|
});
|
|
432
|
-
overlay
|
|
498
|
+
const unbindKeys = bindModalKeys(overlay, (ev) => {
|
|
433
499
|
if (ev.key === 'Enter' && !isButton(ev.target)) {
|
|
434
500
|
ev.preventDefault();
|
|
435
501
|
close(true);
|
|
@@ -456,6 +522,7 @@ function showInfoModal(title, bodyHtml) {
|
|
|
456
522
|
'</div>');
|
|
457
523
|
find(overlay, '.ve-modal-ok').focus();
|
|
458
524
|
function close() {
|
|
525
|
+
unbindKeys();
|
|
459
526
|
overlay.remove();
|
|
460
527
|
}
|
|
461
528
|
find(overlay, '.ve-modal-ok').addEventListener('click', close);
|
|
@@ -464,8 +531,8 @@ function showInfoModal(title, bodyHtml) {
|
|
|
464
531
|
close();
|
|
465
532
|
}
|
|
466
533
|
});
|
|
467
|
-
overlay
|
|
468
|
-
if (ev.key === 'Enter' || ev.key === 'Escape') {
|
|
534
|
+
const unbindKeys = bindModalKeys(overlay, (ev) => {
|
|
535
|
+
if ((ev.key === 'Enter' && !isButton(ev.target)) || ev.key === 'Escape') {
|
|
469
536
|
ev.preventDefault();
|
|
470
537
|
close();
|
|
471
538
|
}
|
|
@@ -618,6 +685,7 @@ function showJsonDiffModal(beforeText, afterText, onConfirm) {
|
|
|
618
685
|
}
|
|
619
686
|
}
|
|
620
687
|
function close(confirmed) {
|
|
688
|
+
unbindKeys();
|
|
621
689
|
overlay.remove();
|
|
622
690
|
if (confirmed && typeof onConfirm === 'function') {
|
|
623
691
|
onConfirm();
|
|
@@ -631,7 +699,7 @@ function showJsonDiffModal(beforeText, afterText, onConfirm) {
|
|
|
631
699
|
close(false);
|
|
632
700
|
}
|
|
633
701
|
});
|
|
634
|
-
overlay
|
|
702
|
+
const unbindKeys = bindModalKeys(overlay, (ev) => {
|
|
635
703
|
if (ev.key === 'ArrowLeft' && hasChanges) {
|
|
636
704
|
ev.preventDefault();
|
|
637
705
|
if (currentChange > 0) {
|
|
@@ -1789,6 +1857,9 @@ function startVariablesEditor({ templateLess }) {
|
|
|
1789
1857
|
let valuesRawMode = activeTab === 'values' && valuesJsonModeFromUrl();
|
|
1790
1858
|
let rawMode = false;
|
|
1791
1859
|
let dirty = false;
|
|
1860
|
+
// A save's PUT and its reload are under way: another Save would diff against the baseline the
|
|
1861
|
+
// first one is about to replace, and send a second full replacement.
|
|
1862
|
+
let saving = false;
|
|
1792
1863
|
let editSeq = 0;
|
|
1793
1864
|
let banner = null;
|
|
1794
1865
|
// Last-known saved JSON for each tab — used to build the post-save GitHub-style diff.
|
|
@@ -1854,7 +1925,8 @@ function startVariablesEditor({ templateLess }) {
|
|
|
1854
1925
|
if (seq !== schemaSeq) {
|
|
1855
1926
|
return false;
|
|
1856
1927
|
}
|
|
1857
|
-
|
|
1928
|
+
// Edits made meanwhile stay on screen, as they do when the load succeeds.
|
|
1929
|
+
showBanner('error', 'Failed to load the schema: ' + escapeHtml(errorMessage(e)), editSeq !== editSeqAtStart);
|
|
1858
1930
|
return false;
|
|
1859
1931
|
}
|
|
1860
1932
|
if (seq !== schemaSeq) {
|
|
@@ -1862,10 +1934,15 @@ function startVariablesEditor({ templateLess }) {
|
|
|
1862
1934
|
}
|
|
1863
1935
|
// An error answer (e.g. 503 with no dist service) is not a schema: kept as one, it rendered as
|
|
1864
1936
|
// "No local __template_variables.json found" and offered to create a file over the real one.
|
|
1937
|
+
// Unless the tab was edited while the load ran: the edits, and the schema they were made on,
|
|
1938
|
+
// are kept on screen (the failed reload emptied the tab and the typing was gone).
|
|
1865
1939
|
if (!r.ok) {
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1940
|
+
const edited = editSeq !== editSeqAtStart;
|
|
1941
|
+
if (!edited) {
|
|
1942
|
+
schemaState = null;
|
|
1943
|
+
schemaRows = [];
|
|
1944
|
+
}
|
|
1945
|
+
showBanner('error', 'Failed to load the schema: ' + escapeHtml((data && data.error) || 'HTTP ' + r.status), edited);
|
|
1869
1946
|
return false;
|
|
1870
1947
|
}
|
|
1871
1948
|
const freshRows = data.schema && Array.isArray(data.schema.tags) ? data.schema.tags.map((t) => Object.assign({}, t)) : [];
|
|
@@ -1883,6 +1960,19 @@ function startVariablesEditor({ templateLess }) {
|
|
|
1883
1960
|
}
|
|
1884
1961
|
async function loadValues(seq, editSeqAtStart) {
|
|
1885
1962
|
let r;
|
|
1963
|
+
// A save is a full replacement. Keep the state explicitly unavailable after a failed read so
|
|
1964
|
+
// the user cannot accidentally submit an empty replacement set — unless the tab was edited
|
|
1965
|
+
// while the load ran: the edits, and the values they were made on, are kept on screen (the
|
|
1966
|
+
// failed reload emptied the tab and the typing was gone, the tab still dirty).
|
|
1967
|
+
const failed = (html) => {
|
|
1968
|
+
const edited = editSeq !== editSeqAtStart;
|
|
1969
|
+
if (!edited) {
|
|
1970
|
+
valuesState = null;
|
|
1971
|
+
valueRows = [];
|
|
1972
|
+
}
|
|
1973
|
+
showBanner('error', html, edited);
|
|
1974
|
+
return false;
|
|
1975
|
+
};
|
|
1886
1976
|
try {
|
|
1887
1977
|
r = await fetch('/@api/variables/values');
|
|
1888
1978
|
}
|
|
@@ -1890,22 +1980,14 @@ function startVariablesEditor({ templateLess }) {
|
|
|
1890
1980
|
if (seq !== valuesSeq) {
|
|
1891
1981
|
return false;
|
|
1892
1982
|
}
|
|
1893
|
-
|
|
1894
|
-
valueRows = [];
|
|
1895
|
-
showBanner('error', 'Failed to load values: ' + escapeHtml(errorMessage(e)));
|
|
1896
|
-
return false;
|
|
1983
|
+
return failed('Failed to load values: ' + escapeHtml(errorMessage(e)));
|
|
1897
1984
|
}
|
|
1898
1985
|
if (!r.ok) {
|
|
1899
1986
|
const err = (await r.json().catch(() => ({})));
|
|
1900
1987
|
if (seq !== valuesSeq) {
|
|
1901
1988
|
return false;
|
|
1902
1989
|
}
|
|
1903
|
-
|
|
1904
|
-
// read so the user cannot accidentally submit an empty replacement set.
|
|
1905
|
-
valuesState = null;
|
|
1906
|
-
valueRows = [];
|
|
1907
|
-
showBanner('error', escapeHtml(err.error || 'Failed to load values.'));
|
|
1908
|
-
return false;
|
|
1990
|
+
return failed(escapeHtml(err.error || 'Failed to load values.'));
|
|
1909
1991
|
}
|
|
1910
1992
|
let data;
|
|
1911
1993
|
try {
|
|
@@ -1915,10 +1997,7 @@ function startVariablesEditor({ templateLess }) {
|
|
|
1915
1997
|
if (seq !== valuesSeq) {
|
|
1916
1998
|
return false;
|
|
1917
1999
|
}
|
|
1918
|
-
|
|
1919
|
-
valueRows = [];
|
|
1920
|
-
showBanner('error', 'Failed to parse the values response: ' + escapeHtml(errorMessage(e)));
|
|
1921
|
-
return false;
|
|
2000
|
+
return failed('Failed to parse the values response: ' + escapeHtml(errorMessage(e)));
|
|
1922
2001
|
}
|
|
1923
2002
|
if (seq !== valuesSeq) {
|
|
1924
2003
|
return false;
|
|
@@ -2104,11 +2183,30 @@ function startVariablesEditor({ templateLess }) {
|
|
|
2104
2183
|
rerenderKeepingAdvancedOpen(i);
|
|
2105
2184
|
});
|
|
2106
2185
|
}
|
|
2186
|
+
/**
|
|
2187
|
+
* The error for the first row whose additional_options text is not JSON, or null. Such a row
|
|
2188
|
+
* holds the text as typed, as a string: serialized for raw mode or a save, it became a valid
|
|
2189
|
+
* JSON string, and the rows raw mode makes back are new objects, no longer marked as invalid —
|
|
2190
|
+
* so `{bad` was saved as `"{bad"`.
|
|
2191
|
+
*/
|
|
2192
|
+
function invalidAdditionalOptionsError() {
|
|
2193
|
+
const index = schemaRows.findIndex((row) => invalidAdditionalOptionsRows.has(row));
|
|
2194
|
+
if (index === -1) {
|
|
2195
|
+
return null;
|
|
2196
|
+
}
|
|
2197
|
+
const tagName = schemaRows[index].name || 'row ' + (index + 1);
|
|
2198
|
+
return 'additional_options must be valid JSON for "' + escapeHtml(tagName) + '".';
|
|
2199
|
+
}
|
|
2107
2200
|
function toggleRawMode() {
|
|
2108
2201
|
if (!schemaState) {
|
|
2109
2202
|
return;
|
|
2110
2203
|
}
|
|
2111
2204
|
if (!rawMode) {
|
|
2205
|
+
const invalid = invalidAdditionalOptionsError();
|
|
2206
|
+
if (invalid) {
|
|
2207
|
+
showBanner('error', invalid + ' Fix it before switching to raw JSON.', true);
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2112
2210
|
// Entering raw mode: serialize the current table state so edits aren't lost.
|
|
2113
2211
|
schemaState.raw = JSON.stringify(Object.assign({}, schemaState.schema, { tags: schemaRows }), null, 2);
|
|
2114
2212
|
}
|
|
@@ -2468,10 +2566,9 @@ function startVariablesEditor({ templateLess }) {
|
|
|
2468
2566
|
raw = byId('raw-editor').value;
|
|
2469
2567
|
}
|
|
2470
2568
|
else {
|
|
2471
|
-
const
|
|
2472
|
-
if (
|
|
2473
|
-
|
|
2474
|
-
showBanner('error', 'additional_options must be valid JSON for "' + escapeHtml(tagName) + '".');
|
|
2569
|
+
const invalid = invalidAdditionalOptionsError();
|
|
2570
|
+
if (invalid) {
|
|
2571
|
+
showBanner('error', invalid);
|
|
2475
2572
|
return;
|
|
2476
2573
|
}
|
|
2477
2574
|
raw = JSON.stringify(Object.assign({}, schemaState.schema, { tags: schemaRows }), null, 2);
|
|
@@ -2481,7 +2578,21 @@ function startVariablesEditor({ templateLess }) {
|
|
|
2481
2578
|
showBanner('success', 'No JSON changes to save.');
|
|
2482
2579
|
return;
|
|
2483
2580
|
}
|
|
2484
|
-
showJsonDiffModal(beforeText, raw, () =>
|
|
2581
|
+
showJsonDiffModal(beforeText, raw, () => commitSave(() => commitSchemaSave(raw)));
|
|
2582
|
+
}
|
|
2583
|
+
/** Runs a confirmed save, with Save off until it and its reload are done (see `saving`). */
|
|
2584
|
+
function commitSave(commit) {
|
|
2585
|
+
setSaving(true);
|
|
2586
|
+
void commit()
|
|
2587
|
+
.catch(showSaveError)
|
|
2588
|
+
.finally(() => setSaving(false));
|
|
2589
|
+
}
|
|
2590
|
+
function setSaving(value) {
|
|
2591
|
+
saving = value;
|
|
2592
|
+
const saveBtn = byId('save-btn');
|
|
2593
|
+
if (saveBtn) {
|
|
2594
|
+
saveBtn.disabled = value;
|
|
2595
|
+
}
|
|
2485
2596
|
}
|
|
2486
2597
|
// Every way a save can fail ends in a visible error: a lost save must never look like a pending
|
|
2487
2598
|
// one. The banner leaves the tab as it is (preserveContent), so the unsaved edits, raw JSON drafts
|
|
@@ -2546,13 +2657,15 @@ function startVariablesEditor({ templateLess }) {
|
|
|
2546
2657
|
const message = warnings.length
|
|
2547
2658
|
? 'Saved, with warning(s):<ul>' + warnings.map((w) => '<li>' + escapeHtml(w) + '</li>').join('') + '</ul>'
|
|
2548
2659
|
: 'Schema saved.';
|
|
2660
|
+
// What the file holds now, also with newer edits: the next diff compares with it (it showed
|
|
2661
|
+
// the lines just saved as changes again).
|
|
2662
|
+
jsonBaseline.schema = raw;
|
|
2549
2663
|
if (hasNewerEdits) {
|
|
2550
2664
|
showBanner('warning', message + '<div>Newer edits remain unsaved.</div>', true);
|
|
2551
2665
|
return;
|
|
2552
2666
|
}
|
|
2553
2667
|
setDirty(false);
|
|
2554
2668
|
showBanner(warnings.length ? 'warning' : 'success', message);
|
|
2555
|
-
jsonBaseline.schema = raw;
|
|
2556
2669
|
await reloadAfterSave('schema');
|
|
2557
2670
|
}
|
|
2558
2671
|
// ── Values tab ───────────────────────────────────────────────────────────────
|
|
@@ -2924,17 +3037,22 @@ function startVariablesEditor({ templateLess }) {
|
|
|
2924
3037
|
closeImportModal();
|
|
2925
3038
|
}
|
|
2926
3039
|
});
|
|
2927
|
-
|
|
3040
|
+
document.body.appendChild(overlay);
|
|
3041
|
+
// On the document, not the overlay: with nothing focused in the dialog (its error state, a
|
|
3042
|
+
// click on its text) Escape reached nothing.
|
|
3043
|
+
unbindImportKeys = bindModalKeys(overlay, (ev) => {
|
|
2928
3044
|
if (ev.key === 'Escape') {
|
|
2929
3045
|
ev.preventDefault();
|
|
2930
3046
|
closeImportModal();
|
|
2931
3047
|
}
|
|
2932
3048
|
});
|
|
2933
|
-
document.body.appendChild(overlay);
|
|
2934
3049
|
void renderImportPickerStep(overlay);
|
|
2935
3050
|
}
|
|
3051
|
+
let unbindImportKeys = null;
|
|
2936
3052
|
function closeImportModal() {
|
|
2937
3053
|
const overlay = byId('ve-import-overlay');
|
|
3054
|
+
unbindImportKeys?.();
|
|
3055
|
+
unbindImportKeys = null;
|
|
2938
3056
|
if (overlay) {
|
|
2939
3057
|
overlay.remove();
|
|
2940
3058
|
}
|
|
@@ -3121,7 +3239,9 @@ function startVariablesEditor({ templateLess }) {
|
|
|
3121
3239
|
showBanner('error', 'Cannot save values until live variables have loaded successfully.');
|
|
3122
3240
|
return;
|
|
3123
3241
|
}
|
|
3124
|
-
|
|
3242
|
+
// A copy: the table edits its rows in place, so with the rows themselves, what was typed while
|
|
3243
|
+
// the PUT ran went into the saved baseline (and MI's values) the save records afterwards.
|
|
3244
|
+
let tags = valueRows.map((row) => ({ ...row }));
|
|
3125
3245
|
const beforeText = jsonBaseline.values;
|
|
3126
3246
|
if (valuesRawMode) {
|
|
3127
3247
|
try {
|
|
@@ -3130,7 +3250,9 @@ function startVariablesEditor({ templateLess }) {
|
|
|
3130
3250
|
tags = fromExportableValueRows(JSON.parse(byId('values-raw-editor').value));
|
|
3131
3251
|
}
|
|
3132
3252
|
catch (e) {
|
|
3133
|
-
|
|
3253
|
+
// The draft stays as typed: re-rendered, the textarea was made again from the last loaded
|
|
3254
|
+
// rows, and the whole draft was gone.
|
|
3255
|
+
showBanner('error', 'Raw JSON is not valid: ' + escapeHtml(errorMessage(e)), true);
|
|
3134
3256
|
return;
|
|
3135
3257
|
}
|
|
3136
3258
|
}
|
|
@@ -3140,7 +3262,7 @@ function startVariablesEditor({ templateLess }) {
|
|
|
3140
3262
|
showBanner('success', 'No JSON changes to save.');
|
|
3141
3263
|
return;
|
|
3142
3264
|
}
|
|
3143
|
-
showJsonDiffModal(beforeText, afterText, () =>
|
|
3265
|
+
showJsonDiffModal(beforeText, afterText, () => commitSave(() => commitValuesSave(tags, afterText)));
|
|
3144
3266
|
}
|
|
3145
3267
|
async function commitValuesSave(tags, afterText) {
|
|
3146
3268
|
const saveEditSeq = editSeq;
|
|
@@ -3170,16 +3292,27 @@ function startVariablesEditor({ templateLess }) {
|
|
|
3170
3292
|
}
|
|
3171
3293
|
}
|
|
3172
3294
|
renderLocalUpdateButton();
|
|
3295
|
+
// What MI holds now, also with newer edits: the next diff compares with it, and the JSON
|
|
3296
|
+
// issues bar no longer takes the saved values for ones changed since the load.
|
|
3297
|
+
jsonBaseline.values = afterText;
|
|
3298
|
+
if (valuesState) {
|
|
3299
|
+
valuesState.combined = tags.map((t) => ({ name: t.name, value: t.value }));
|
|
3300
|
+
}
|
|
3301
|
+
// The JSON issues bar may have been worked out while the PUT ran, against the old values: the
|
|
3302
|
+
// values just saved stayed flagged as changed until the next keystroke.
|
|
3303
|
+
refreshValuesJsonIssues();
|
|
3173
3304
|
if (hasNewerEdits) {
|
|
3174
3305
|
showBanner('warning', message + '<div>Newer edits remain unsaved.</div>', true);
|
|
3175
3306
|
return;
|
|
3176
3307
|
}
|
|
3177
3308
|
setDirty(false);
|
|
3178
3309
|
showBanner(warnings.length ? 'warning' : 'success', message);
|
|
3179
|
-
jsonBaseline.values = afterText;
|
|
3180
3310
|
await reloadAfterSave('values');
|
|
3181
3311
|
}
|
|
3182
3312
|
function save() {
|
|
3313
|
+
if (saving) {
|
|
3314
|
+
return;
|
|
3315
|
+
}
|
|
3183
3316
|
(activeTab === 'schema' ? saveSchema() : saveValues()).catch(showSaveError);
|
|
3184
3317
|
}
|
|
3185
3318
|
// ── Local pages ──────────────────────────────────────────────────────────────
|