collabmd 0.1.37 → 0.1.39

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.
Files changed (81) hide show
  1. package/README.md +1 -0
  2. package/dist/client/assets/{CodeMirrorEditor-UCMRAMCV-DnBV8gCm.js → CodeMirrorEditor-UCMRAMCV-75Zi8U_A.js} +1 -1
  3. package/dist/client/assets/{dist-BPEi1dEF.js → dist-1SMeCZhf.js} +1 -1
  4. package/dist/client/assets/{dist-Dbg8P4C5.js → dist-8EAyH6_p.js} +1 -1
  5. package/dist/client/assets/{dist-BEKQo64E.js → dist-B7XQNod9.js} +1 -1
  6. package/dist/client/assets/dist-BCzW2jDH.js +1 -0
  7. package/dist/client/assets/{dist-CQVTTk81.js → dist-BEk5kTf_.js} +1 -1
  8. package/dist/client/assets/dist-BOn786F3.js +1 -0
  9. package/dist/client/assets/{dist-Bn8WRPs1.js → dist-BXiFOexW.js} +1 -1
  10. package/dist/client/assets/{dist-v3i1ifes.js → dist-Bp_j2hKC.js} +1 -1
  11. package/dist/client/assets/dist-BwUX6zpo.js +1 -0
  12. package/dist/client/assets/{dist-DwvSvkOE.js → dist-Bz-8_JmE.js} +1 -1
  13. package/dist/client/assets/{dist-CBEw2W2P.js → dist-CH3l6nVf.js} +1 -1
  14. package/dist/client/assets/{dist-DQiph4qx.js → dist-CIt_ogHO.js} +1 -1
  15. package/dist/client/assets/{dist-BOP-dC-R.js → dist-CQZDuOJ9.js} +1 -1
  16. package/dist/client/assets/{dist-DdAqzGFU.js → dist-CRyP6okk.js} +1 -1
  17. package/dist/client/assets/dist-D-ixVAsS2.js +1 -0
  18. package/dist/client/assets/{dist-Brrlevif.js → dist-D8d0IGSI.js} +1 -1
  19. package/dist/client/assets/{dist-DZ2t5lUX.js → dist-Das5WX49.js} +1 -1
  20. package/dist/client/assets/{dist-CaMl1R6P.js → dist-DplP0BiK.js} +1 -1
  21. package/dist/client/assets/{dist-BE8C_ouo.js → dist-DwgKARew.js} +1 -1
  22. package/dist/client/assets/dist-GgGgSSbl.js +1 -0
  23. package/dist/client/assets/{dist-D34Xzrah.js → dist-NcJIpTJ8.js} +1 -1
  24. package/dist/client/assets/dist-QktK6LIH.js +1 -0
  25. package/dist/client/assets/{dist-BiCgLddE.js → dist-ZuMKSK7c.js} +1 -1
  26. package/dist/client/assets/{dist-DPCUH5MO.js → dist-_THc9HiJ.js} +1 -1
  27. package/dist/client/assets/{dist-mPPUaHhq.js → dist-bkoMCmF7.js} +1 -1
  28. package/dist/client/assets/{dist-B98fHn5m.js → dist-eAgBpkKv.js} +1 -1
  29. package/dist/client/assets/{dist-BScEL4VP.js → dist-fpFkgu34.js} +1 -1
  30. package/dist/client/assets/{dist-aE0_04fl.js → dist-tRNsnttY.js} +1 -1
  31. package/dist/client/assets/{dist-DWRyOl2w.js → dist-uyVGTnZh.js} +1 -1
  32. package/dist/client/assets/{dist-CJWCCWEW.js → dist-yy8SO13g.js} +1 -1
  33. package/dist/client/assets/{drawio-editor-B65ntu5E.js → drawio-editor-B_Sr8Z_Z.js} +1 -1
  34. package/dist/client/assets/{drawioEditor-ChF0ZKUh.js → drawioEditor-DBSyP3_d.js} +2 -2
  35. package/dist/client/assets/editor-session-C1scHGLl.js +16 -0
  36. package/dist/client/assets/{embedded-editor-base-CgEkjNYn.css → embedded-editor-base-Bb8ywn1e.css} +1 -1
  37. package/dist/client/assets/excalidraw-editor-B0XXVLHd.js +9 -0
  38. package/dist/client/assets/{excalidraw-scene-MyNAJi2A.js → excalidraw-scene-wAbVSY1L.js} +2 -2
  39. package/dist/client/assets/{excalidrawEditor-BT9hmN5y.js → excalidrawEditor-37sk03H8.js} +2 -2
  40. package/dist/client/assets/{export-page-9MSUmliH.js → export-page-Bnk-YQp-.js} +1 -1
  41. package/dist/client/assets/{exportDocument-W3oYiIU3.js → exportDocument-WQNApAbg.js} +2 -2
  42. package/dist/client/assets/{index-D8NvI6X_.js → index-BoKmcqkH.js} +2 -2
  43. package/dist/client/assets/{main-CuQn72Cc.js → main-BKumyd95.js} +5 -5
  44. package/dist/client/assets/preview-render-compiler-9SmLUP5V.js +1 -0
  45. package/dist/client/assets/{preview-render-compiler-BCaqHl2M.js → preview-render-compiler-IUtgIlOM.js} +1 -1
  46. package/dist/client/assets/{preview-render-worker-yfayDDfY.js → preview-render-worker-BwQ5sPy9.js} +1 -1
  47. package/dist/client/assets/runtime-config-BcUbZKho.js +1 -0
  48. package/dist/client/assets/{vault-api-client-CGLEvxZo.js → vault-api-client-BDKKvtOF.js} +1 -1
  49. package/dist/client/drawio-editor.html +1 -1
  50. package/dist/client/excalidraw-editor.html +1 -1
  51. package/dist/client/export-document.html +1 -1
  52. package/dist/client/index.html +1 -1
  53. package/docker-compose.yml +1 -0
  54. package/package.json +6 -6
  55. package/src/client/application/preview-render-compiler.js +12 -1
  56. package/src/client/application/preview-render-executor.js +4 -0
  57. package/src/client/application/preview-render-worker.js +2 -0
  58. package/src/client/application/preview-renderer.js +5 -2
  59. package/src/client/application/wiki-link-file-controller.js +7 -0
  60. package/src/client/bootstrap/collabmd-app-shell.js +2 -0
  61. package/src/client/domain/excalidraw-collaboration.js +50 -0
  62. package/src/client/domain/excalidraw-scene-reconcile.js +38 -0
  63. package/src/client/domain/mermaid-language.js +179 -0
  64. package/src/client/domain/runtime-paths.js +1 -0
  65. package/src/client/excalidraw-editor.js +76 -47
  66. package/src/client/infrastructure/editor-view-adapter.js +7 -2
  67. package/src/client/infrastructure/excalidraw-room-client.js +139 -27
  68. package/src/client/styles/surfaces/embedded-editor-base.css +2 -1
  69. package/src/domain/excalidraw-room-codec.js +1 -17
  70. package/src/server/config/env.js +1 -0
  71. package/src/server/infrastructure/http/create-static-handler.js +2 -0
  72. package/dist/client/assets/dist-BfVljg0L2.js +0 -1
  73. package/dist/client/assets/dist-CcG3Y7Wp.js +0 -1
  74. package/dist/client/assets/dist-CghBwrO3.js +0 -1
  75. package/dist/client/assets/dist-Csbx9lQ-.js +0 -1
  76. package/dist/client/assets/dist-DLyohOXi.js +0 -1
  77. package/dist/client/assets/dist-WXLmfDGg.js +0 -1
  78. package/dist/client/assets/editor-session-YtGVwK3v.js +0 -16
  79. package/dist/client/assets/excalidraw-editor-Co4KOVfJ.js +0 -9
  80. package/dist/client/assets/preview-render-compiler-kjMajWKn.js +0 -1
  81. package/dist/client/assets/runtime-config-Bf8nxVez.js +0 -1
@@ -0,0 +1,38 @@
1
+ export function buildReconciledExcalidrawSceneUpdate({
2
+ appStateOverrides = {},
3
+ currentAppState,
4
+ currentElements = [],
5
+ documentViewState = {},
6
+ includeUnchangedAppState = false,
7
+ reconcileElementsFn,
8
+ restoreAppStateFn,
9
+ restoreElementsFn,
10
+ scene,
11
+ theme = 'dark',
12
+ } = {}) {
13
+ const restoredElements = restoreElementsFn(scene?.elements || [], currentElements, {
14
+ repairBindings: true,
15
+ });
16
+ const restoredAppState = restoreAppStateFn(scene?.appState || {}, currentAppState);
17
+ const nextAppState = {
18
+ theme,
19
+ viewBackgroundColor: restoredAppState.viewBackgroundColor ?? '#ffffff',
20
+ gridSize: restoredAppState.gridSize ?? null,
21
+ ...documentViewState,
22
+ ...appStateOverrides,
23
+ };
24
+ const appStateUpdate = Object.fromEntries(
25
+ Object.entries(nextAppState).filter(([key, value]) => (
26
+ includeUnchangedAppState || currentAppState?.[key] !== value
27
+ )),
28
+ );
29
+ const update = {
30
+ elements: reconcileElementsFn(currentElements, restoredElements, currentAppState),
31
+ };
32
+
33
+ if (Object.keys(appStateUpdate).length > 0) {
34
+ update.appState = appStateUpdate;
35
+ }
36
+
37
+ return update;
38
+ }
@@ -0,0 +1,179 @@
1
+ import { LanguageDescription, LanguageSupport, StreamLanguage } from '@codemirror/language';
2
+
3
+ const DIAGRAM_DECLARATIONS = new Set([
4
+ 'block-beta',
5
+ 'classdiagram',
6
+ 'erdiagram',
7
+ 'flowchart',
8
+ 'gantt',
9
+ 'gitgraph',
10
+ 'graph',
11
+ 'journey',
12
+ 'mindmap',
13
+ 'pie',
14
+ 'quadrantchart',
15
+ 'sankey-beta',
16
+ 'sequencediagram',
17
+ 'statediagram',
18
+ 'statediagram-v2',
19
+ 'timeline',
20
+ 'xychart-beta',
21
+ ]);
22
+
23
+ const KEYWORDS = new Set([
24
+ 'accdescr',
25
+ 'acctitle',
26
+ 'actor',
27
+ 'alt',
28
+ 'and',
29
+ 'autonumber',
30
+ 'break',
31
+ 'callback',
32
+ 'class',
33
+ 'classdef',
34
+ 'click',
35
+ 'critical',
36
+ 'dateformat',
37
+ 'direction',
38
+ 'else',
39
+ 'end',
40
+ 'exclude',
41
+ 'include',
42
+ 'interpolate',
43
+ 'link',
44
+ 'linkstyle',
45
+ 'loop',
46
+ 'note',
47
+ 'opt',
48
+ 'par',
49
+ 'participant',
50
+ 'rect',
51
+ 'section',
52
+ 'state',
53
+ 'style',
54
+ 'subgraph',
55
+ 'theme',
56
+ 'title',
57
+ ]);
58
+
59
+ const BOOLEAN_LITERALS = new Set([
60
+ 'false',
61
+ 'no',
62
+ 'off',
63
+ 'on',
64
+ 'true',
65
+ 'yes',
66
+ ]);
67
+
68
+ const FLOW_DIRECTIONS = new Set([
69
+ 'bt',
70
+ 'lr',
71
+ 'rl',
72
+ 'tb',
73
+ 'td',
74
+ ]);
75
+
76
+ function readWord(stream) {
77
+ const match = stream.match(/[A-Za-z_][\w.]*(?:-[A-Za-z0-9_][\w.]*)*/u, false);
78
+ if (!match) {
79
+ return '';
80
+ }
81
+
82
+ stream.match(/[A-Za-z_][\w.]*(?:-[A-Za-z0-9_][\w.]*)*/u);
83
+ return match[0];
84
+ }
85
+
86
+ function readDirective(stream) {
87
+ if (!stream.match('%%{')) {
88
+ return false;
89
+ }
90
+
91
+ if (stream.skipTo('}%%')) {
92
+ stream.match('}%%');
93
+ } else {
94
+ stream.skipToEnd();
95
+ }
96
+
97
+ return true;
98
+ }
99
+
100
+ const mermaidStreamLanguage = StreamLanguage.define({
101
+ languageData: {
102
+ commentTokens: {
103
+ line: '%%',
104
+ },
105
+ },
106
+ token(stream) {
107
+ if (stream.eatSpace()) {
108
+ return null;
109
+ }
110
+
111
+ if (readDirective(stream)) {
112
+ return 'meta';
113
+ }
114
+
115
+ if (stream.match('%%')) {
116
+ stream.skipToEnd();
117
+ return 'comment';
118
+ }
119
+
120
+ if (stream.match(/"(?:[^"\\]|\\.)*"?/u) || stream.match(/'(?:[^'\\]|\\.)*'?/u)) {
121
+ return 'string';
122
+ }
123
+
124
+ if (stream.match(/#[0-9a-f]{3,8}\b/iu) || stream.match(/#[A-Za-z][\w-]*/u)) {
125
+ return 'atom';
126
+ }
127
+
128
+ if (stream.match(/\b\d+(?:\.\d+)?%?\b/u)) {
129
+ return 'number';
130
+ }
131
+
132
+ if (stream.match(/[ox*+<#]?[-=.\\/]+(?:left|right|up|down)?[-=.\\/]*[ox*+>#]?/iu)) {
133
+ return 'operator';
134
+ }
135
+
136
+ if (stream.match(/[{}[\]():,;|]/u)) {
137
+ return 'punctuation';
138
+ }
139
+
140
+ const word = readWord(stream);
141
+ if (word) {
142
+ const normalized = word.toLowerCase();
143
+
144
+ if (DIAGRAM_DECLARATIONS.has(normalized)) {
145
+ return 'meta';
146
+ }
147
+
148
+ if (KEYWORDS.has(normalized)) {
149
+ return 'keyword';
150
+ }
151
+
152
+ if (BOOLEAN_LITERALS.has(normalized)) {
153
+ return 'bool';
154
+ }
155
+
156
+ if (FLOW_DIRECTIONS.has(normalized)) {
157
+ return 'atom';
158
+ }
159
+
160
+ if (/^[A-Z][A-Z0-9_]*$/u.test(word)) {
161
+ return 'typeName';
162
+ }
163
+
164
+ return 'variableName';
165
+ }
166
+
167
+ stream.next();
168
+ return null;
169
+ },
170
+ });
171
+
172
+ export const mermaidLanguage = new LanguageSupport(mermaidStreamLanguage);
173
+
174
+ export const mermaidLanguageDescription = LanguageDescription.of({
175
+ alias: ['mermaid', 'mmd'],
176
+ extensions: ['mermaid', 'mmd'],
177
+ name: 'Mermaid',
178
+ support: mermaidLanguage,
179
+ });
@@ -79,6 +79,7 @@ export function getClientRuntimeConfig() {
79
79
  environment: 'development',
80
80
  gitEnabled: true,
81
81
  publicWsBaseUrl: '',
82
+ wikiLinkAutoCreate: true,
82
83
  wsBasePath: '/ws',
83
84
  ...(window.__COLLABMD_CONFIG__ ?? {}),
84
85
  };
@@ -10,7 +10,9 @@ import {
10
10
  import '@excalidraw/excalidraw/index.css';
11
11
 
12
12
  import {
13
+ buildRenderableCollaboratorsMap,
13
14
  findCollaboratorByPeerId,
15
+ getCollaboratorsRenderSignature,
14
16
  mergeAwarenessUserPatch,
15
17
  resolveLocalAwarenessUser,
16
18
  } from './domain/excalidraw-collaboration.js';
@@ -24,6 +26,9 @@ import {
24
26
  parseSceneJson,
25
27
  sceneToInitialData,
26
28
  } from './domain/excalidraw-scene.js';
29
+ import {
30
+ buildReconciledExcalidrawSceneUpdate,
31
+ } from './domain/excalidraw-scene-reconcile.js';
27
32
  import {
28
33
  normalizeDocumentMode,
29
34
  } from './domain/excalidraw-document-switch.js';
@@ -60,6 +65,7 @@ let pendingHostFollowPeerId = null;
60
65
  let suppressViewportBroadcast = false;
61
66
  let pendingViewportSuppressionReleases = 0;
62
67
  let lastAppliedFollowViewportSignature = '';
68
+ let lastRenderedCollaboratorsSignature = '';
63
69
  let apiStateCleanupCallbacks = [];
64
70
  let collaboratorRenderFrame = 0;
65
71
  let queuedCollaborators = null;
@@ -295,16 +301,21 @@ if (isTestMode) {
295
301
 
296
302
  function applyCollaborators(collaborators) {
297
303
  activeCollaborators = collaborators instanceof Map ? collaborators : new Map();
304
+ const renderableCollaborators = buildRenderableCollaboratorsMap(activeCollaborators);
305
+ const renderSignature = getCollaboratorsRenderSignature(renderableCollaborators);
298
306
 
299
307
  if (!excalidrawAPI) {
300
308
  pendingCollaborators = activeCollaborators;
301
309
  return;
302
310
  }
303
311
 
304
- excalidrawAPI.updateScene({
305
- collaborators: activeCollaborators,
306
- captureUpdate: CaptureUpdateAction.NEVER,
307
- });
312
+ if (renderSignature !== lastRenderedCollaboratorsSignature) {
313
+ lastRenderedCollaboratorsSignature = renderSignature;
314
+ excalidrawAPI.updateScene({
315
+ collaborators: renderableCollaborators,
316
+ captureUpdate: CaptureUpdateAction.NEVER,
317
+ });
318
+ }
308
319
 
309
320
  if (pendingHostFollowPeerId) {
310
321
  applyHostFollowRequest(pendingHostFollowPeerId);
@@ -328,10 +339,29 @@ function queueCollaboratorsRender(collaborators) {
328
339
  });
329
340
  }
330
341
 
331
- function applySceneFromJson(rawJson) {
342
+ function isEditingTextElement() {
343
+ return Boolean(excalidrawAPI?.getAppState?.()?.editingTextElement);
344
+ }
345
+
346
+ function flushPendingRemoteScene() {
347
+ if (!pendingRemoteSceneJson || !excalidrawAPI || !collabReady || isEditingTextElement()) {
348
+ return false;
349
+ }
350
+
351
+ const sceneJson = pendingRemoteSceneJson;
352
+ pendingRemoteSceneJson = '';
353
+ applySceneFromJson(sceneJson, {
354
+ force: true,
355
+ });
356
+ return true;
357
+ }
358
+
359
+ function applySceneFromJson(rawJson, {
360
+ force = false,
361
+ } = {}) {
332
362
  const scene = parseSceneJson(rawJson);
333
363
  const normalizedJson = JSON.stringify(scene);
334
- if (normalizedJson === appliedSceneJson && !pendingRemoteSceneJson) {
364
+ if (!force && normalizedJson === appliedSceneJson && !pendingRemoteSceneJson) {
335
365
  return;
336
366
  }
337
367
 
@@ -342,9 +372,12 @@ function applySceneFromJson(rawJson) {
342
372
  return;
343
373
  }
344
374
 
345
- updateApiScene(scene, {
346
- replaceElements: true,
347
- });
375
+ if (!force && isEditingTextElement()) {
376
+ pendingRemoteSceneJson = normalizedJson;
377
+ return;
378
+ }
379
+
380
+ updateApiScene(scene);
348
381
  }
349
382
 
350
383
  function releaseOnChangeSuppressionAfterPaint({ trackedSharedSnapshot = false } = {}) {
@@ -376,30 +409,21 @@ function releaseViewportBroadcastSuppressionAfterPaint() {
376
409
 
377
410
  function buildApiSceneUpdate(scene, {
378
411
  appStateOverrides = {},
379
- replaceElements = false,
380
412
  } = {}) {
381
413
  const currentAppState = excalidrawAPI.getAppState();
382
- const currentElements = replaceElements
383
- ? []
384
- : (excalidrawAPI.getSceneElementsIncludingDeleted?.() || excalidrawAPI.getSceneElements());
385
- const restoredElements = restoreElements(scene?.elements || [], currentElements, {
386
- repairBindings: true,
387
- });
388
- const restoredAppState = restoreAppState(scene?.appState || {}, currentAppState);
389
- const reconciledElements = replaceElements
390
- ? restoredElements
391
- : reconcileElements(currentElements, restoredElements, currentAppState);
414
+ const currentElements = excalidrawAPI.getSceneElementsIncludingDeleted?.() || excalidrawAPI.getSceneElements();
392
415
 
393
- return {
394
- appState: {
395
- theme: currentTheme,
396
- viewBackgroundColor: restoredAppState.viewBackgroundColor ?? '#ffffff',
397
- gridSize: restoredAppState.gridSize ?? null,
398
- ...getDocumentViewState(),
399
- ...appStateOverrides,
400
- },
401
- elements: reconciledElements,
402
- };
416
+ return buildReconciledExcalidrawSceneUpdate({
417
+ appStateOverrides,
418
+ currentAppState,
419
+ currentElements,
420
+ documentViewState: getDocumentViewState(),
421
+ reconcileElementsFn: reconcileElements,
422
+ restoreAppStateFn: restoreAppState,
423
+ restoreElementsFn: restoreElements,
424
+ scene,
425
+ theme: currentTheme,
426
+ });
403
427
  }
404
428
 
405
429
  function logFileConflictOnce(conflictingFileIds = []) {
@@ -437,12 +461,10 @@ function requestEditorRemount(scene) {
437
461
  function applySceneToMountedApi(scene, {
438
462
  appStateOverrides = {},
439
463
  captureUpdate = CaptureUpdateAction.NEVER,
440
- replaceElements = false,
441
464
  trackedSharedSnapshot = false,
442
465
  } = {}) {
443
466
  const nextSceneUpdate = buildApiSceneUpdate(scene, {
444
467
  appStateOverrides,
445
- replaceElements,
446
468
  });
447
469
  let applyResult = null;
448
470
 
@@ -484,13 +506,11 @@ function applySceneToMountedApi(scene, {
484
506
  function updateApiScene(scene, {
485
507
  appStateOverrides = {},
486
508
  captureUpdate = CaptureUpdateAction.NEVER,
487
- replaceElements = false,
488
509
  trackedSharedSnapshot = true,
489
510
  } = {}) {
490
511
  applySceneToMountedApi(scene, {
491
512
  appStateOverrides,
492
513
  captureUpdate,
493
- replaceElements,
494
514
  trackedSharedSnapshot,
495
515
  });
496
516
  }
@@ -520,6 +540,10 @@ function onRoomTextUpdate() {
520
540
  applySceneFromJson(roomClient?.getLastSceneJson?.() || '');
521
541
  }
522
542
 
543
+ function getLiveSceneElementsForSync(fallbackElements = []) {
544
+ return excalidrawAPI?.getSceneElementsIncludingDeleted?.() || fallbackElements;
545
+ }
546
+
523
547
  function postToParent(type, payload = {}) {
524
548
  window.parent.postMessage({ source: 'excalidraw-editor', type, ...payload }, parentOrigin);
525
549
  }
@@ -674,6 +698,7 @@ function resetRealtimeRoomState() {
674
698
  suppressViewportBroadcast = false;
675
699
  pendingViewportSuppressionReleases = 0;
676
700
  lastAppliedFollowViewportSignature = '';
701
+ lastRenderedCollaboratorsSignature = '';
677
702
  if (collaboratorRenderFrame) {
678
703
  cancelAnimationFrame(collaboratorRenderFrame);
679
704
  }
@@ -799,17 +824,8 @@ function scheduleSyncToRoom(elements, appState, files) {
799
824
  return;
800
825
  }
801
826
 
802
- const nextSceneJson = JSON.stringify(normalizeScene({
803
- elements,
804
- appState,
805
- files,
806
- }));
807
- if (nextSceneJson === appliedSceneJson) {
808
- return;
809
- }
810
-
811
- appliedSceneJson = nextSceneJson;
812
- roomClient.scheduleSceneSync(elements, appState, files);
827
+ const liveElements = getLiveSceneElementsForSync(elements);
828
+ roomClient.scheduleSceneSync(liveElements, appState, files);
813
829
  }
814
830
 
815
831
  function initializeEditor(api) {
@@ -828,14 +844,27 @@ function initializeEditor(api) {
828
844
 
829
845
  setFollowedSocket(null, { force: true });
830
846
  }));
847
+ apiStateCleanupCallbacks.push(api.onStateChange('editingTextElement', (editingTextElement) => {
848
+ if (!editingTextElement) {
849
+ requestAnimationFrame(() => {
850
+ requestAnimationFrame(() => {
851
+ flushPendingRemoteScene();
852
+ });
853
+ });
854
+ }
855
+ }));
831
856
 
832
857
  const sceneJson = pendingRemoteSceneJson || roomClient?.getLastSceneJson?.() || '';
858
+ const initialScene = parseSceneJson(sceneJson);
833
859
  pendingRemoteSceneJson = '';
834
- updateApiScene(parseSceneJson(sceneJson));
860
+ appliedSceneJson = JSON.stringify(initialScene);
861
+ updateApiScene(initialScene);
835
862
 
836
863
  if (pendingCollaborators) {
864
+ const renderableCollaborators = buildRenderableCollaboratorsMap(pendingCollaborators);
865
+ lastRenderedCollaboratorsSignature = getCollaboratorsRenderSignature(renderableCollaborators);
837
866
  excalidrawAPI.updateScene({
838
- collaborators: pendingCollaborators,
867
+ collaborators: renderableCollaborators,
839
868
  captureUpdate: CaptureUpdateAction.NEVER,
840
869
  });
841
870
  pendingCollaborators = null;
@@ -38,14 +38,15 @@ import {
38
38
  } from '@codemirror/view';
39
39
  import { yCollab } from 'y-codemirror.next';
40
40
 
41
- import { isBaseFilePath, isPlantUmlFilePath } from '../../domain/file-kind.js';
41
+ import { isBaseFilePath, isMermaidFilePath, isPlantUmlFilePath } from '../../domain/file-kind.js';
42
42
  import { normalizeCommentQuote } from '../../domain/comment-threads.js';
43
43
  import { createMarkdownToolbarEdit } from '../domain/markdown-formatting.js';
44
+ import { mermaidLanguage, mermaidLanguageDescription } from '../domain/mermaid-language.js';
44
45
  import { wikiLinkCompletions } from '../domain/wiki-link-completions.js';
45
46
  import { plantUmlLanguage, plantUmlLanguageDescription } from '../domain/plantuml-language.js';
46
47
  import { handleImagePasteEvent } from './editor-paste-utils.js';
47
48
 
48
- const markdownCodeLanguages = [...languages, plantUmlLanguageDescription];
49
+ const markdownCodeLanguages = [...languages, mermaidLanguageDescription, plantUmlLanguageDescription];
49
50
  const pairedMatchingBracketMark = Decoration.mark({ class: 'cm-matchingBracket cm-matchingBracket-paired' });
50
51
  const nonmatchingBracketMark = Decoration.mark({ class: 'cm-nonmatchingBracket' });
51
52
  const remoteUpdateMark = Decoration.mark({ class: 'cm-remoteUpdateFlash' });
@@ -299,6 +300,10 @@ function createEditorTheme(theme) {
299
300
  }
300
301
 
301
302
  export function createLanguageExtension(filePath) {
303
+ if (isMermaidFilePath(filePath)) {
304
+ return mermaidLanguage;
305
+ }
306
+
302
307
  if (isPlantUmlFilePath(filePath)) {
303
308
  return plantUmlLanguage;
304
309
  }