veryfront 0.1.29 → 0.1.31

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/esm/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-template.d.ts","sourceRoot":"","sources":["../../../src/src/studio/bridge-template.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAy/H/E"}
1
+ {"version":3,"file":"bridge-template.d.ts","sourceRoot":"","sources":["../../../src/src/studio/bridge-template.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAyqI/E"}
@@ -69,6 +69,13 @@ export function generateStudioBridgeScript(options) {
69
69
  let markdownLatestPresenceUsers = [];
70
70
  let markdownLatestSelections = [];
71
71
  let markdownHasUnsavedChanges = false;
72
+ let markdownSaveInProgress = false;
73
+ let markdownYDoc = null;
74
+ let markdownYProvider = null;
75
+ let markdownYText = null;
76
+ let markdownYjsConnected = false;
77
+ let markdownYjsSetupId = 0;
78
+ const LEXICAL_YJS_ORIGIN = 'lexical-yjs-binding';
72
79
 
73
80
  const MARKDOWN_SLASH_COMMANDS = [
74
81
  {
@@ -1409,6 +1416,142 @@ export function generateStudioBridgeScript(options) {
1409
1416
  }, 120);
1410
1417
  }
1411
1418
 
1419
+ function computeTextDiff(oldText, newText) {
1420
+ var prefixLen = 0;
1421
+ var minLen = Math.min(oldText.length, newText.length);
1422
+ while (prefixLen < minLen && oldText.charCodeAt(prefixLen) === newText.charCodeAt(prefixLen)) {
1423
+ prefixLen++;
1424
+ }
1425
+ var suffixLen = 0;
1426
+ var maxSuffix = minLen - prefixLen;
1427
+ while (suffixLen < maxSuffix &&
1428
+ oldText.charCodeAt(oldText.length - 1 - suffixLen) === newText.charCodeAt(newText.length - 1 - suffixLen)) {
1429
+ suffixLen++;
1430
+ }
1431
+ return {
1432
+ index: prefixLen,
1433
+ deleteCount: oldText.length - prefixLen - suffixLen,
1434
+ insertText: newText.slice(prefixLen, suffixLen > 0 ? newText.length - suffixLen : undefined)
1435
+ };
1436
+ }
1437
+
1438
+ function syncLocalChangeToYText(fullContent) {
1439
+ if (!markdownYText || !markdownYDoc) {
1440
+ return;
1441
+ }
1442
+ var currentYContent = markdownYText.toString();
1443
+ if (currentYContent === fullContent) {
1444
+ return;
1445
+ }
1446
+ var diff = computeTextDiff(currentYContent, fullContent);
1447
+ if (diff.deleteCount === 0 && diff.insertText === '') {
1448
+ return;
1449
+ }
1450
+ markdownYDoc.transact(function() {
1451
+ if (diff.deleteCount > 0) {
1452
+ markdownYText.delete(diff.index, diff.deleteCount);
1453
+ }
1454
+ if (diff.insertText) {
1455
+ markdownYText.insert(diff.index, diff.insertText);
1456
+ }
1457
+ }, LEXICAL_YJS_ORIGIN);
1458
+ }
1459
+
1460
+ function setupMarkdownYjsConnection(config) {
1461
+ if (markdownYDoc) {
1462
+ return;
1463
+ }
1464
+
1465
+ var setupId = ++markdownYjsSetupId;
1466
+
1467
+ Promise.all([
1468
+ import('https://esm.sh/yjs@13.6.28?target=es2022'),
1469
+ import('https://esm.sh/y-websocket@2.1.0?target=es2022')
1470
+ ]).then(function(modules) {
1471
+ // Abort if edit mode was closed while imports were loading
1472
+ if (setupId !== markdownYjsSetupId) {
1473
+ return;
1474
+ }
1475
+
1476
+ var Y = modules[0];
1477
+ var WebsocketProvider = modules[1].WebsocketProvider;
1478
+
1479
+ var doc = new Y.Doc({ guid: config.guid });
1480
+ var provider = new WebsocketProvider(config.wsUrl, config.guid, doc, {
1481
+ resyncInterval: -1,
1482
+ params: { token: config.authToken }
1483
+ });
1484
+
1485
+ var ytext = doc.getText(config.fileId);
1486
+
1487
+ markdownYDoc = doc;
1488
+ markdownYProvider = provider;
1489
+ markdownYText = ytext;
1490
+
1491
+ // Filter non-binary messages to prevent y-websocket parse errors
1492
+ provider.on('status', function(event) {
1493
+ console.debug('[StudioBridge] Yjs status:', event.status);
1494
+ if (event.status === 'connected' && provider.ws) {
1495
+ var origOnMessage = provider.ws.onmessage;
1496
+ provider.ws.onmessage = function(wsEvent) {
1497
+ if (typeof wsEvent.data === 'string') {
1498
+ return;
1499
+ }
1500
+ if (origOnMessage) {
1501
+ origOnMessage.call(provider.ws, wsEvent);
1502
+ }
1503
+ };
1504
+ }
1505
+ });
1506
+
1507
+ provider.on('sync', function(synced) {
1508
+ if (synced && !markdownYjsConnected) {
1509
+ markdownYjsConnected = true;
1510
+
1511
+ var ytextContent = ytext.toString();
1512
+ if (markdownCurrentContent && markdownCurrentContent !== ytextContent) {
1513
+ // User typed before sync completed — push local edits to Y.Text
1514
+ syncLocalChangeToYText(markdownCurrentContent);
1515
+ } else if (ytextContent) {
1516
+ // No local edits — seed editor from Y.Text
1517
+ applyMarkdownContent(ytextContent);
1518
+ }
1519
+
1520
+ // Observe Y.Text for remote changes (from other users / Monaco)
1521
+ ytext.observe(function(event) {
1522
+ if (event.transaction.origin === LEXICAL_YJS_ORIGIN) {
1523
+ return;
1524
+ }
1525
+ var fullContent = ytext.toString();
1526
+ if (fullContent === markdownCurrentContent) {
1527
+ return;
1528
+ }
1529
+ applyMarkdownContent(fullContent);
1530
+ });
1531
+
1532
+ console.debug('[StudioBridge] Yjs synced, bound to Y.Text for fileId:', config.fileId);
1533
+ }
1534
+ });
1535
+ }).catch(function(error) {
1536
+ console.error('[StudioBridge] Failed to setup Yjs connection:', error);
1537
+ });
1538
+ }
1539
+
1540
+ function disposeMarkdownYjs() {
1541
+ markdownYjsSetupId++;
1542
+ if (markdownYProvider) {
1543
+ markdownYProvider.disconnect();
1544
+ markdownYProvider.destroy();
1545
+ markdownYProvider = null;
1546
+ }
1547
+ if (markdownYDoc) {
1548
+ markdownYDoc.destroy();
1549
+ markdownYDoc = null;
1550
+ }
1551
+ markdownYText = null;
1552
+ markdownYjsConnected = false;
1553
+ }
1554
+
1412
1555
  function getTextOffsetWithinRoot(root, targetNode, targetOffset) {
1413
1556
  if (!root || !targetNode) {
1414
1557
  return 0;
@@ -2908,7 +3051,11 @@ export function generateStudioBridgeScript(options) {
2908
3051
  }
2909
3052
  markdownCurrentContent = fullContent;
2910
3053
  markdownHasUnsavedChanges = true;
2911
- scheduleMarkdownSync(fullContent);
3054
+ if (markdownYjsConnected) {
3055
+ syncLocalChangeToYText(fullContent);
3056
+ } else {
3057
+ scheduleMarkdownSync(fullContent);
3058
+ }
2912
3059
  scheduleMarkdownSelectionOverlayRender();
2913
3060
  }
2914
3061
 
@@ -2916,6 +3063,7 @@ export function generateStudioBridgeScript(options) {
2916
3063
  if (!markdownHasUnsavedChanges) {
2917
3064
  return;
2918
3065
  }
3066
+ markdownSaveInProgress = true;
2919
3067
  setMarkdownPersistStatus('saving');
2920
3068
  if (markdownSyncTimer) {
2921
3069
  clearTimeout(markdownSyncTimer);
@@ -3068,7 +3216,8 @@ export function generateStudioBridgeScript(options) {
3068
3216
  return;
3069
3217
  }
3070
3218
 
3071
- if (markdownLexicalApi && markdownLexicalRenderedContent === content) {
3219
+ if (markdownLexicalApi && (markdownLexicalRenderedContent === content || markdownCurrentContent === content)) {
3220
+ console.debug('[StudioBridge] applyMarkdownContent: skipped (content unchanged)');
3072
3221
  markdownCurrentContent = content;
3073
3222
  scheduleMarkdownSelectionOverlayRender();
3074
3223
  scheduleMarkdownSlashMenuUpdate();
@@ -3077,6 +3226,8 @@ export function generateStudioBridgeScript(options) {
3077
3226
  return;
3078
3227
  }
3079
3228
 
3229
+ console.debug('[StudioBridge] applyMarkdownContent: rebuilding Lexical DOM, content length:', content.length, 'rendered match:', markdownLexicalRenderedContent === content, 'current match:', markdownCurrentContent === content);
3230
+
3080
3231
  const mdxImportMap = parseMdxImportMap(content);
3081
3232
  const parts = extractMarkdownParts(content);
3082
3233
  const extracted = extractRawBlocksForEditor(parts.body, mdxImportMap);
@@ -3706,6 +3857,7 @@ export function generateStudioBridgeScript(options) {
3706
3857
  markdownOverlaySelections = [];
3707
3858
  clearMarkdownSelectionOverlay();
3708
3859
  clearMarkdownSelectionSync();
3860
+ disposeMarkdownYjs();
3709
3861
  }
3710
3862
 
3711
3863
  const nextUrl = new URL(window.location.href);
@@ -3936,9 +4088,30 @@ export function generateStudioBridgeScript(options) {
3936
4088
  if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
3937
4089
  return;
3938
4090
  }
4091
+ if (markdownYjsConnected) {
4092
+ return;
4093
+ }
3939
4094
  applyMarkdownContent(message.content || '');
3940
4095
  return;
3941
4096
 
4097
+ case 'initYjsConnection':
4098
+ if (!isMarkdownPage()) {
4099
+ return;
4100
+ }
4101
+ if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
4102
+ return;
4103
+ }
4104
+ if (message.initialContent) {
4105
+ applyMarkdownContent(message.initialContent);
4106
+ }
4107
+ setupMarkdownYjsConnection({
4108
+ wsUrl: message.wsUrl,
4109
+ guid: message.guid,
4110
+ fileId: message.fileId || markdownFileId,
4111
+ authToken: message.authToken
4112
+ });
4113
+ return;
4114
+
3942
4115
  case 'setMarkdownPersistState':
3943
4116
  if (!isMarkdownPage()) {
3944
4117
  return;
@@ -3946,9 +4119,12 @@ export function generateStudioBridgeScript(options) {
3946
4119
  if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
3947
4120
  return;
3948
4121
  }
3949
- setMarkdownPersistStatus(message.status || 'saved');
3950
- if (message.status === 'saved') {
3951
- markdownHasUnsavedChanges = false;
4122
+ if (markdownSaveInProgress) {
4123
+ setMarkdownPersistStatus(message.status || 'saved');
4124
+ if (message.status === 'saved' || message.status === 'error') {
4125
+ markdownSaveInProgress = false;
4126
+ markdownHasUnsavedChanges = false;
4127
+ }
3952
4128
  }
3953
4129
  return;
3954
4130
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -77,6 +77,13 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
77
77
  let markdownLatestPresenceUsers = [];
78
78
  let markdownLatestSelections = [];
79
79
  let markdownHasUnsavedChanges = false;
80
+ let markdownSaveInProgress = false;
81
+ let markdownYDoc = null;
82
+ let markdownYProvider = null;
83
+ let markdownYText = null;
84
+ let markdownYjsConnected = false;
85
+ let markdownYjsSetupId = 0;
86
+ const LEXICAL_YJS_ORIGIN = 'lexical-yjs-binding';
80
87
 
81
88
  const MARKDOWN_SLASH_COMMANDS = [
82
89
  {
@@ -1417,6 +1424,142 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
1417
1424
  }, 120);
1418
1425
  }
1419
1426
 
1427
+ function computeTextDiff(oldText, newText) {
1428
+ var prefixLen = 0;
1429
+ var minLen = Math.min(oldText.length, newText.length);
1430
+ while (prefixLen < minLen && oldText.charCodeAt(prefixLen) === newText.charCodeAt(prefixLen)) {
1431
+ prefixLen++;
1432
+ }
1433
+ var suffixLen = 0;
1434
+ var maxSuffix = minLen - prefixLen;
1435
+ while (suffixLen < maxSuffix &&
1436
+ oldText.charCodeAt(oldText.length - 1 - suffixLen) === newText.charCodeAt(newText.length - 1 - suffixLen)) {
1437
+ suffixLen++;
1438
+ }
1439
+ return {
1440
+ index: prefixLen,
1441
+ deleteCount: oldText.length - prefixLen - suffixLen,
1442
+ insertText: newText.slice(prefixLen, suffixLen > 0 ? newText.length - suffixLen : undefined)
1443
+ };
1444
+ }
1445
+
1446
+ function syncLocalChangeToYText(fullContent) {
1447
+ if (!markdownYText || !markdownYDoc) {
1448
+ return;
1449
+ }
1450
+ var currentYContent = markdownYText.toString();
1451
+ if (currentYContent === fullContent) {
1452
+ return;
1453
+ }
1454
+ var diff = computeTextDiff(currentYContent, fullContent);
1455
+ if (diff.deleteCount === 0 && diff.insertText === '') {
1456
+ return;
1457
+ }
1458
+ markdownYDoc.transact(function() {
1459
+ if (diff.deleteCount > 0) {
1460
+ markdownYText.delete(diff.index, diff.deleteCount);
1461
+ }
1462
+ if (diff.insertText) {
1463
+ markdownYText.insert(diff.index, diff.insertText);
1464
+ }
1465
+ }, LEXICAL_YJS_ORIGIN);
1466
+ }
1467
+
1468
+ function setupMarkdownYjsConnection(config) {
1469
+ if (markdownYDoc) {
1470
+ return;
1471
+ }
1472
+
1473
+ var setupId = ++markdownYjsSetupId;
1474
+
1475
+ Promise.all([
1476
+ import('https://esm.sh/yjs@13.6.28?target=es2022'),
1477
+ import('https://esm.sh/y-websocket@2.1.0?target=es2022')
1478
+ ]).then(function(modules) {
1479
+ // Abort if edit mode was closed while imports were loading
1480
+ if (setupId !== markdownYjsSetupId) {
1481
+ return;
1482
+ }
1483
+
1484
+ var Y = modules[0];
1485
+ var WebsocketProvider = modules[1].WebsocketProvider;
1486
+
1487
+ var doc = new Y.Doc({ guid: config.guid });
1488
+ var provider = new WebsocketProvider(config.wsUrl, config.guid, doc, {
1489
+ resyncInterval: -1,
1490
+ params: { token: config.authToken }
1491
+ });
1492
+
1493
+ var ytext = doc.getText(config.fileId);
1494
+
1495
+ markdownYDoc = doc;
1496
+ markdownYProvider = provider;
1497
+ markdownYText = ytext;
1498
+
1499
+ // Filter non-binary messages to prevent y-websocket parse errors
1500
+ provider.on('status', function(event) {
1501
+ console.debug('[StudioBridge] Yjs status:', event.status);
1502
+ if (event.status === 'connected' && provider.ws) {
1503
+ var origOnMessage = provider.ws.onmessage;
1504
+ provider.ws.onmessage = function(wsEvent) {
1505
+ if (typeof wsEvent.data === 'string') {
1506
+ return;
1507
+ }
1508
+ if (origOnMessage) {
1509
+ origOnMessage.call(provider.ws, wsEvent);
1510
+ }
1511
+ };
1512
+ }
1513
+ });
1514
+
1515
+ provider.on('sync', function(synced) {
1516
+ if (synced && !markdownYjsConnected) {
1517
+ markdownYjsConnected = true;
1518
+
1519
+ var ytextContent = ytext.toString();
1520
+ if (markdownCurrentContent && markdownCurrentContent !== ytextContent) {
1521
+ // User typed before sync completed — push local edits to Y.Text
1522
+ syncLocalChangeToYText(markdownCurrentContent);
1523
+ } else if (ytextContent) {
1524
+ // No local edits — seed editor from Y.Text
1525
+ applyMarkdownContent(ytextContent);
1526
+ }
1527
+
1528
+ // Observe Y.Text for remote changes (from other users / Monaco)
1529
+ ytext.observe(function(event) {
1530
+ if (event.transaction.origin === LEXICAL_YJS_ORIGIN) {
1531
+ return;
1532
+ }
1533
+ var fullContent = ytext.toString();
1534
+ if (fullContent === markdownCurrentContent) {
1535
+ return;
1536
+ }
1537
+ applyMarkdownContent(fullContent);
1538
+ });
1539
+
1540
+ console.debug('[StudioBridge] Yjs synced, bound to Y.Text for fileId:', config.fileId);
1541
+ }
1542
+ });
1543
+ }).catch(function(error) {
1544
+ console.error('[StudioBridge] Failed to setup Yjs connection:', error);
1545
+ });
1546
+ }
1547
+
1548
+ function disposeMarkdownYjs() {
1549
+ markdownYjsSetupId++;
1550
+ if (markdownYProvider) {
1551
+ markdownYProvider.disconnect();
1552
+ markdownYProvider.destroy();
1553
+ markdownYProvider = null;
1554
+ }
1555
+ if (markdownYDoc) {
1556
+ markdownYDoc.destroy();
1557
+ markdownYDoc = null;
1558
+ }
1559
+ markdownYText = null;
1560
+ markdownYjsConnected = false;
1561
+ }
1562
+
1420
1563
  function getTextOffsetWithinRoot(root, targetNode, targetOffset) {
1421
1564
  if (!root || !targetNode) {
1422
1565
  return 0;
@@ -2916,7 +3059,11 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
2916
3059
  }
2917
3060
  markdownCurrentContent = fullContent;
2918
3061
  markdownHasUnsavedChanges = true;
2919
- scheduleMarkdownSync(fullContent);
3062
+ if (markdownYjsConnected) {
3063
+ syncLocalChangeToYText(fullContent);
3064
+ } else {
3065
+ scheduleMarkdownSync(fullContent);
3066
+ }
2920
3067
  scheduleMarkdownSelectionOverlayRender();
2921
3068
  }
2922
3069
 
@@ -2924,6 +3071,7 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
2924
3071
  if (!markdownHasUnsavedChanges) {
2925
3072
  return;
2926
3073
  }
3074
+ markdownSaveInProgress = true;
2927
3075
  setMarkdownPersistStatus('saving');
2928
3076
  if (markdownSyncTimer) {
2929
3077
  clearTimeout(markdownSyncTimer);
@@ -3076,7 +3224,8 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
3076
3224
  return;
3077
3225
  }
3078
3226
 
3079
- if (markdownLexicalApi && markdownLexicalRenderedContent === content) {
3227
+ if (markdownLexicalApi && (markdownLexicalRenderedContent === content || markdownCurrentContent === content)) {
3228
+ console.debug('[StudioBridge] applyMarkdownContent: skipped (content unchanged)');
3080
3229
  markdownCurrentContent = content;
3081
3230
  scheduleMarkdownSelectionOverlayRender();
3082
3231
  scheduleMarkdownSlashMenuUpdate();
@@ -3085,6 +3234,8 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
3085
3234
  return;
3086
3235
  }
3087
3236
 
3237
+ console.debug('[StudioBridge] applyMarkdownContent: rebuilding Lexical DOM, content length:', content.length, 'rendered match:', markdownLexicalRenderedContent === content, 'current match:', markdownCurrentContent === content);
3238
+
3088
3239
  const mdxImportMap = parseMdxImportMap(content);
3089
3240
  const parts = extractMarkdownParts(content);
3090
3241
  const extracted = extractRawBlocksForEditor(parts.body, mdxImportMap);
@@ -3714,6 +3865,7 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
3714
3865
  markdownOverlaySelections = [];
3715
3866
  clearMarkdownSelectionOverlay();
3716
3867
  clearMarkdownSelectionSync();
3868
+ disposeMarkdownYjs();
3717
3869
  }
3718
3870
 
3719
3871
  const nextUrl = new URL(window.location.href);
@@ -3944,9 +4096,30 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
3944
4096
  if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
3945
4097
  return;
3946
4098
  }
4099
+ if (markdownYjsConnected) {
4100
+ return;
4101
+ }
3947
4102
  applyMarkdownContent(message.content || '');
3948
4103
  return;
3949
4104
 
4105
+ case 'initYjsConnection':
4106
+ if (!isMarkdownPage()) {
4107
+ return;
4108
+ }
4109
+ if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
4110
+ return;
4111
+ }
4112
+ if (message.initialContent) {
4113
+ applyMarkdownContent(message.initialContent);
4114
+ }
4115
+ setupMarkdownYjsConnection({
4116
+ wsUrl: message.wsUrl,
4117
+ guid: message.guid,
4118
+ fileId: message.fileId || markdownFileId,
4119
+ authToken: message.authToken
4120
+ });
4121
+ return;
4122
+
3950
4123
  case 'setMarkdownPersistState':
3951
4124
  if (!isMarkdownPage()) {
3952
4125
  return;
@@ -3954,9 +4127,12 @@ export function generateStudioBridgeScript(options: StudioBridgeOptions): string
3954
4127
  if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
3955
4128
  return;
3956
4129
  }
3957
- setMarkdownPersistStatus(message.status || 'saved');
3958
- if (message.status === 'saved') {
3959
- markdownHasUnsavedChanges = false;
4130
+ if (markdownSaveInProgress) {
4131
+ setMarkdownPersistStatus(message.status || 'saved');
4132
+ if (message.status === 'saved' || message.status === 'error') {
4133
+ markdownSaveInProgress = false;
4134
+ markdownHasUnsavedChanges = false;
4135
+ }
3960
4136
  }
3961
4137
  return;
3962
4138