collabmd 0.1.31 → 0.1.32
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/dist/client/assets/{drawio-editor-B4sHHwWJ.js → drawio-editor-Br4qyh3r.js} +1 -1
- package/dist/client/assets/drawioEditor-ClmnTx5J.js +2 -0
- package/dist/client/assets/{editor-session-BKHEYkbY.js → editor-session-Cf-iV5-3.js} +2 -2
- package/dist/client/assets/embedded-editor-base-CgEkjNYn.css +1 -0
- package/dist/client/assets/{excalidraw-editor-CHbeNZu6.js → excalidraw-editor-CXAxVhlw.js} +2 -2
- package/dist/client/assets/{excalidrawEditor-BhaTX2Ko.js → excalidrawEditor-DoH_kMdu.js} +2 -2
- package/dist/client/assets/exportDocument-9wTrBZNS.css +1 -0
- package/dist/client/assets/{index-kPgEYvEX.js → index-C8QZYHvV.js} +2 -2
- package/dist/client/assets/index-CxSbUTs2.css +1 -0
- package/dist/client/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/client/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/client/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/client/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/client/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/client/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/client/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/client/assets/main-BjHHMlv0.js +1249 -0
- package/dist/client/assets/vault-api-client-Bf9tB_GW.js +1 -0
- package/dist/client/assets/vault-api-client-D7Ck0CvR.css +1 -0
- package/dist/client/drawio-editor.html +1 -1
- package/dist/client/excalidraw-editor.html +1 -1
- package/dist/client/export-document.html +2 -2
- package/dist/client/index.html +2 -2
- package/package.json +4 -1
- package/src/client/app/main-entry.js +2 -0
- package/src/client/application/app-shell/git-feature.js +28 -36
- package/src/client/application/app-shell/presence-feature.js +3 -1
- package/src/client/application/diagram-preview-hydrator.js +24 -15
- package/src/client/application/mermaid-preview-hydrator.js +121 -4
- package/src/client/application/plantuml-preview-hydrator.js +6 -16
- package/src/client/application/preview-renderer.js +11 -2
- package/src/client/bootstrap/collabmd-app-shell.js +176 -50
- package/src/client/domain/excalidraw-document-switch.js +3 -0
- package/src/client/excalidraw-editor.js +187 -84
- package/src/client/infrastructure/auth-client.js +4 -4
- package/src/client/infrastructure/backlinks-api-client.js +18 -0
- package/src/client/infrastructure/comment-thread-store.js +4 -0
- package/src/client/infrastructure/editor-session.js +2 -0
- package/src/client/infrastructure/git-api-client.js +134 -0
- package/src/client/infrastructure/plantuml-api-client.js +21 -0
- package/src/client/infrastructure/vault-api-client.js +67 -0
- package/src/client/presentation/backlinks-panel.js +6 -9
- package/src/client/presentation/bases-preview-controller.js +26 -7
- package/src/client/presentation/excalidraw-embed-controller.js +251 -15
- package/src/client/presentation/excalidraw-embed-reconciler.js +0 -1
- package/src/client/presentation/file-action-controller.js +97 -0
- package/src/client/presentation/file-explorer-controller.js +2 -0
- package/src/client/presentation/file-explorer-view.js +368 -0
- package/src/client/presentation/file-history-view-controller.js +41 -40
- package/src/client/presentation/git-diff-view-controller.js +70 -53
- package/src/client/presentation/git-panel-controller.js +12 -30
- package/src/client/styles/export-document.css +45 -2
- package/src/client/styles/features/auth-gate.css +13 -24
- package/src/client/styles/features/comment-card.css +6 -0
- package/src/client/styles/features/comments-drawer.css +38 -18
- package/src/client/styles/features/diagram-preview.css +33 -0
- package/src/client/styles/features/embedded-preview.css +37 -11
- package/src/client/styles/features/preview-markdown.css +103 -28
- package/src/client/styles/foundation/themes.css +4 -0
- package/src/client/styles/foundation/tokens.css +4 -3
- package/src/client/styles/layout/sidebar.css +72 -0
- package/src/server/auth/auth-common.js +224 -0
- package/src/server/auth/auth-constants.js +30 -0
- package/src/server/auth/auth-strategies.js +320 -0
- package/src/server/auth/create-auth-service.js +24 -545
- package/src/server/domain/bases/base-definition.js +255 -0
- package/src/server/domain/bases/base-expression-runtime.js +1159 -0
- package/src/server/domain/bases/base-index-snapshot-store.js +418 -0
- package/src/server/domain/bases/base-query-results.js +205 -0
- package/src/server/domain/bases/base-query-service.js +68 -1965
- package/src/server/domain/collaboration/collaboration-room.js +36 -1
- package/src/server/domain/collaboration/room-client-state-store.js +1 -0
- package/src/server/domain/collaboration/room-persistence-controller.js +1 -1
- package/src/server/infrastructure/http/create-vault-api-query-handler.js +109 -1
- package/src/server/infrastructure/http/http-response.js +56 -0
- package/src/server/infrastructure/persistence/vault-file-store.js +110 -0
- package/dist/client/assets/drawioEditor-DGpo2hcx.js +0 -2
- package/dist/client/assets/embedded-editor-base-DgmRcsR8.css +0 -1
- package/dist/client/assets/exportDocument-DmV7Gngb.css +0 -1
- package/dist/client/assets/index-D69HOZHr.css +0 -1
- package/dist/client/assets/main-DSxNTtD-.js +0 -1243
- package/dist/client/assets/vault-api-client-BzHQseMN.js +0 -1
- package/dist/client/assets/vault-api-client-De7kMvrE.css +0 -1
- /package/dist/client/assets/{exportDocument-CtZKWE2c.js → exportDocument-Bia2hI25.js} +0 -0
|
@@ -21,17 +21,22 @@ import {
|
|
|
21
21
|
parseSceneJson,
|
|
22
22
|
sceneToInitialData,
|
|
23
23
|
} from './domain/excalidraw-scene.js';
|
|
24
|
+
import {
|
|
25
|
+
normalizeDocumentMode,
|
|
26
|
+
} from './domain/excalidraw-document-switch.js';
|
|
24
27
|
import { ensureClientAuthenticated } from './infrastructure/auth-client.js';
|
|
25
28
|
import { ExcalidrawRoomClient } from './infrastructure/excalidraw-room-client.js';
|
|
26
29
|
import { vaultApiClient } from './infrastructure/vault-api-client.js';
|
|
27
30
|
|
|
28
31
|
const params = new URLSearchParams(window.location.search);
|
|
29
|
-
const filePath = params.get('file');
|
|
30
32
|
const isTestMode = params.get('test') === '1';
|
|
31
|
-
const isPreviewMode = params.get('mode') === 'preview';
|
|
32
33
|
const parentOrigin = window.location.origin;
|
|
33
34
|
const syncTimeoutMs = Number.parseInt(params.get('syncTimeoutMs') || '', 10);
|
|
34
35
|
|
|
36
|
+
let currentDocument = {
|
|
37
|
+
filePath: params.get('file') || '',
|
|
38
|
+
mode: normalizeDocumentMode(params.get('mode')),
|
|
39
|
+
};
|
|
35
40
|
let excalidrawAPI = null;
|
|
36
41
|
let currentTheme = params.get('theme') || 'dark';
|
|
37
42
|
let localAwarenessUser = resolveLocalAwarenessUser({
|
|
@@ -55,22 +60,104 @@ let apiStateCleanupCallbacks = [];
|
|
|
55
60
|
let collaboratorRenderFrame = 0;
|
|
56
61
|
let queuedCollaborators = null;
|
|
57
62
|
let initialViewportFitPending = true;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (!collabReady) {
|
|
62
|
-
pendingCollaborators = collaborators;
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
63
|
+
let roomClient = null;
|
|
64
|
+
let roomClientGeneration = 0;
|
|
65
|
+
let reactRoot = null;
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
function getDocumentViewState(mode = currentDocument.mode) {
|
|
68
|
+
const normalizedMode = normalizeDocumentMode(mode);
|
|
69
|
+
return {
|
|
70
|
+
viewModeEnabled: normalizedMode === 'preview',
|
|
71
|
+
zenModeEnabled: normalizedMode === 'preview',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function createRoomClient(filePath) {
|
|
76
|
+
const generation = ++roomClientGeneration;
|
|
77
|
+
const client = new ExcalidrawRoomClient({
|
|
78
|
+
filePath,
|
|
79
|
+
onCollaboratorsChange: (collaborators) => {
|
|
80
|
+
if (generation !== roomClientGeneration) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!collabReady) {
|
|
85
|
+
pendingCollaborators = collaborators;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
queueCollaboratorsRender(collaborators);
|
|
90
|
+
},
|
|
91
|
+
onRemoteSceneJson: (sceneJson) => {
|
|
92
|
+
if (generation !== roomClientGeneration) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
applySceneFromJson(sceneJson);
|
|
97
|
+
},
|
|
98
|
+
syncTimeoutMs: Number.isFinite(syncTimeoutMs) ? syncTimeoutMs : undefined,
|
|
99
|
+
vaultClient: vaultApiClient,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
client,
|
|
104
|
+
generation,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildExcalidrawProps({ initialData } = {}) {
|
|
109
|
+
const props = {
|
|
110
|
+
onMount: handleEditorMount,
|
|
111
|
+
onInitialize: (api) => {
|
|
112
|
+
initializeEditor(api);
|
|
113
|
+
},
|
|
114
|
+
onUnmount: () => {
|
|
115
|
+
clearEditorApiStateBindings();
|
|
116
|
+
disconnectRealtimeRoom({ preserveEditorBindings: true });
|
|
117
|
+
excalidrawAPI = null;
|
|
118
|
+
collabReady = false;
|
|
119
|
+
},
|
|
120
|
+
aiEnabled: false,
|
|
121
|
+
isCollaborating: true,
|
|
122
|
+
onChange: (elements, appState, files) => {
|
|
123
|
+
scheduleSyncToRoom(elements, appState, files);
|
|
124
|
+
roomClient?.syncLocalSelectionAwareness(appState);
|
|
125
|
+
},
|
|
126
|
+
onPointerUpdate: (payload) => {
|
|
127
|
+
roomClient?.scheduleLocalPointerAwareness(payload);
|
|
128
|
+
},
|
|
129
|
+
theme: currentTheme,
|
|
130
|
+
...getDocumentViewState(),
|
|
131
|
+
UIOptions: {
|
|
132
|
+
canvasActions: {
|
|
133
|
+
export: false,
|
|
134
|
+
loadScene: false,
|
|
135
|
+
saveToActiveFile: false,
|
|
136
|
+
toggleTheme: false,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (initialData) {
|
|
142
|
+
props.initialData = initialData;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return props;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function renderExcalidrawApp({ initialData } = {}) {
|
|
149
|
+
if (!reactRoot) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
reactRoot.render(
|
|
154
|
+
React.createElement(
|
|
155
|
+
'div',
|
|
156
|
+
{ style: { height: '100vh', width: '100%' } },
|
|
157
|
+
React.createElement(Excalidraw, buildExcalidrawProps({ initialData })),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
74
161
|
|
|
75
162
|
function applySurfaceTheme(theme = currentTheme) {
|
|
76
163
|
document.body.dataset.theme = theme === 'light' ? 'light' : 'dark';
|
|
@@ -108,7 +195,7 @@ function applyLocalUserPatch(nextUser = {}) {
|
|
|
108
195
|
currentUser: localAwarenessUser,
|
|
109
196
|
nextUser,
|
|
110
197
|
});
|
|
111
|
-
roomClient
|
|
198
|
+
roomClient?.setLocalUser(localAwarenessUser);
|
|
112
199
|
}
|
|
113
200
|
|
|
114
201
|
if (isTestMode) {
|
|
@@ -149,13 +236,13 @@ if (isTestMode) {
|
|
|
149
236
|
} : null;
|
|
150
237
|
},
|
|
151
238
|
isViewMode: () => Boolean(excalidrawAPI?.getAppState?.().viewModeEnabled),
|
|
152
|
-
getSceneJson: () => roomClient
|
|
239
|
+
getSceneJson: () => roomClient?.getLastSceneJson?.() || '',
|
|
153
240
|
isAuthoritativeReady: () => (
|
|
154
241
|
Boolean(excalidrawAPI)
|
|
155
242
|
&& collabReady
|
|
156
|
-
&& roomClient
|
|
157
|
-
&& roomClient
|
|
158
|
-
&& roomClient
|
|
243
|
+
&& roomClient?.canWriteToRoom === true
|
|
244
|
+
&& roomClient?.waitingForAuthoritativeSync === false
|
|
245
|
+
&& roomClient?.isApplyingSharedSnapshot?.() === false
|
|
159
246
|
),
|
|
160
247
|
isReady: () => collabReady && Boolean(excalidrawAPI) && Boolean(getNativeHistoryButton('undo')) && Boolean(getNativeHistoryButton('redo')),
|
|
161
248
|
redoShared: () => triggerNativeHistory('redo'),
|
|
@@ -243,7 +330,7 @@ function releaseOnChangeSuppressionAfterPaint({ trackedSharedSnapshot = false }
|
|
|
243
330
|
requestAnimationFrame(() => {
|
|
244
331
|
pendingSuppressionReleases = Math.max(0, pendingSuppressionReleases - 1);
|
|
245
332
|
if (trackedSharedSnapshot) {
|
|
246
|
-
roomClient
|
|
333
|
+
roomClient?.endApplyingSharedSnapshot();
|
|
247
334
|
}
|
|
248
335
|
if (pendingSuppressionReleases === 0) {
|
|
249
336
|
suppressOnChange = false;
|
|
@@ -264,20 +351,29 @@ function releaseViewportBroadcastSuppressionAfterPaint() {
|
|
|
264
351
|
});
|
|
265
352
|
}
|
|
266
353
|
|
|
267
|
-
function buildApiSceneUpdate(scene
|
|
354
|
+
function buildApiSceneUpdate(scene, {
|
|
355
|
+
appStateOverrides = {},
|
|
356
|
+
replaceElements = false,
|
|
357
|
+
} = {}) {
|
|
268
358
|
const currentAppState = excalidrawAPI.getAppState();
|
|
269
|
-
const currentElements =
|
|
359
|
+
const currentElements = replaceElements
|
|
360
|
+
? []
|
|
361
|
+
: (excalidrawAPI.getSceneElementsIncludingDeleted?.() || excalidrawAPI.getSceneElements());
|
|
270
362
|
const restoredElements = restoreElements(scene?.elements || [], currentElements, {
|
|
271
363
|
repairBindings: true,
|
|
272
364
|
});
|
|
273
365
|
const restoredAppState = restoreAppState(scene?.appState || {}, currentAppState);
|
|
274
|
-
const reconciledElements =
|
|
366
|
+
const reconciledElements = replaceElements
|
|
367
|
+
? restoredElements
|
|
368
|
+
: reconcileElements(currentElements, restoredElements, currentAppState);
|
|
275
369
|
|
|
276
370
|
return {
|
|
277
371
|
appState: {
|
|
278
372
|
theme: currentTheme,
|
|
279
373
|
viewBackgroundColor: restoredAppState.viewBackgroundColor ?? '#ffffff',
|
|
280
374
|
gridSize: restoredAppState.gridSize ?? null,
|
|
375
|
+
...getDocumentViewState(),
|
|
376
|
+
...appStateOverrides,
|
|
281
377
|
},
|
|
282
378
|
elements: reconciledElements,
|
|
283
379
|
files: scene?.files || {},
|
|
@@ -285,14 +381,19 @@ function buildApiSceneUpdate(scene) {
|
|
|
285
381
|
}
|
|
286
382
|
|
|
287
383
|
function updateApiScene(scene, {
|
|
384
|
+
appStateOverrides = {},
|
|
288
385
|
captureUpdate = CaptureUpdateAction.NEVER,
|
|
386
|
+
replaceElements = false,
|
|
289
387
|
trackedSharedSnapshot = true,
|
|
290
388
|
} = {}) {
|
|
291
|
-
const nextSceneUpdate = buildApiSceneUpdate(scene
|
|
389
|
+
const nextSceneUpdate = buildApiSceneUpdate(scene, {
|
|
390
|
+
appStateOverrides,
|
|
391
|
+
replaceElements,
|
|
392
|
+
});
|
|
292
393
|
|
|
293
394
|
suppressOnChange = true;
|
|
294
395
|
if (trackedSharedSnapshot) {
|
|
295
|
-
roomClient
|
|
396
|
+
roomClient?.beginApplyingSharedSnapshot();
|
|
296
397
|
}
|
|
297
398
|
try {
|
|
298
399
|
excalidrawAPI.updateScene({
|
|
@@ -325,14 +426,14 @@ function applyLocalScene(scene, {
|
|
|
325
426
|
});
|
|
326
427
|
|
|
327
428
|
if (suppressOnChange) {
|
|
328
|
-
roomClient
|
|
429
|
+
roomClient?.commitSceneJson(normalizedJson, {
|
|
329
430
|
origin: 'excalidraw-local-scene-apply',
|
|
330
431
|
});
|
|
331
432
|
}
|
|
332
433
|
}
|
|
333
434
|
|
|
334
435
|
function onRoomTextUpdate() {
|
|
335
|
-
applySceneFromJson(roomClient
|
|
436
|
+
applySceneFromJson(roomClient?.getLastSceneJson?.() || '');
|
|
336
437
|
}
|
|
337
438
|
|
|
338
439
|
function postToParent(type, payload = {}) {
|
|
@@ -367,14 +468,14 @@ function scheduleInitialViewportFit() {
|
|
|
367
468
|
animate: false,
|
|
368
469
|
fitToViewport: true,
|
|
369
470
|
maxZoom: 2,
|
|
370
|
-
viewportZoomFactor:
|
|
471
|
+
viewportZoomFactor: normalizeDocumentMode(currentDocument.mode) === 'preview' ? 0.92 : 0.88,
|
|
371
472
|
});
|
|
372
473
|
});
|
|
373
474
|
});
|
|
374
475
|
}
|
|
375
476
|
|
|
376
477
|
function syncLocalViewportToRoom() {
|
|
377
|
-
if (!collabReady || !excalidrawAPI || suppressViewportBroadcast) {
|
|
478
|
+
if (!collabReady || !excalidrawAPI || !roomClient || suppressViewportBroadcast) {
|
|
378
479
|
return;
|
|
379
480
|
}
|
|
380
481
|
|
|
@@ -462,8 +563,16 @@ function applyHostFollowRequest(peerId) {
|
|
|
462
563
|
pendingHostFollowPeerId = null;
|
|
463
564
|
}
|
|
464
565
|
|
|
465
|
-
function
|
|
566
|
+
function clearEditorApiStateBindings() {
|
|
567
|
+
apiStateCleanupCallbacks.forEach((cleanup) => cleanup());
|
|
568
|
+
apiStateCleanupCallbacks = [];
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function resetRealtimeRoomState() {
|
|
466
572
|
collabReady = false;
|
|
573
|
+
suppressOnChange = false;
|
|
574
|
+
pendingRemoteSceneJson = '';
|
|
575
|
+
pendingSuppressionReleases = 0;
|
|
467
576
|
pendingCollaborators = null;
|
|
468
577
|
activeCollaborators = new Map();
|
|
469
578
|
followedSocketId = null;
|
|
@@ -476,9 +585,19 @@ function disconnectRealtimeRoom() {
|
|
|
476
585
|
}
|
|
477
586
|
collaboratorRenderFrame = 0;
|
|
478
587
|
queuedCollaborators = null;
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function disconnectRealtimeRoom({ preserveEditorBindings = false } = {}) {
|
|
591
|
+
const activeRoomClient = roomClient;
|
|
592
|
+
roomClient = null;
|
|
593
|
+
roomClientGeneration += 1;
|
|
594
|
+
|
|
595
|
+
resetRealtimeRoomState();
|
|
596
|
+
if (!preserveEditorBindings) {
|
|
597
|
+
clearEditorApiStateBindings();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
activeRoomClient?.disconnect();
|
|
482
601
|
}
|
|
483
602
|
|
|
484
603
|
let didDisconnectRealtimeRoom = false;
|
|
@@ -499,7 +618,7 @@ async function waitForPendingRoomWrites({
|
|
|
499
618
|
const startedAt = performance.now();
|
|
500
619
|
|
|
501
620
|
while ((performance.now() - startedAt) < maxWaitMs) {
|
|
502
|
-
const ws = roomClient
|
|
621
|
+
const ws = roomClient?.provider?.ws;
|
|
503
622
|
if (!ws || ws.readyState !== WebSocket.OPEN || ws.bufferedAmount === 0) {
|
|
504
623
|
return;
|
|
505
624
|
}
|
|
@@ -509,10 +628,23 @@ async function waitForPendingRoomWrites({
|
|
|
509
628
|
}
|
|
510
629
|
|
|
511
630
|
async function prepareRealtimeRoomDisconnect() {
|
|
512
|
-
roomClient
|
|
631
|
+
roomClient?.flushSceneSync();
|
|
513
632
|
await waitForPendingRoomWrites();
|
|
514
633
|
}
|
|
515
634
|
|
|
635
|
+
async function connectDocumentClient(filePath) {
|
|
636
|
+
const { client, generation } = createRoomClient(filePath);
|
|
637
|
+
roomClient = client;
|
|
638
|
+
const scene = await client.connect({ initialUser: localAwarenessUser });
|
|
639
|
+
|
|
640
|
+
if (generation !== roomClientGeneration || roomClient !== client) {
|
|
641
|
+
client.disconnect();
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return scene;
|
|
646
|
+
}
|
|
647
|
+
|
|
516
648
|
window.addEventListener('pagehide', disconnectRealtimeRoomOnce);
|
|
517
649
|
|
|
518
650
|
window.addEventListener('message', (event) => {
|
|
@@ -536,6 +668,7 @@ window.addEventListener('message', (event) => {
|
|
|
536
668
|
});
|
|
537
669
|
releaseOnChangeSuppressionAfterPaint();
|
|
538
670
|
}
|
|
671
|
+
renderExcalidrawApp();
|
|
539
672
|
return;
|
|
540
673
|
}
|
|
541
674
|
|
|
@@ -556,11 +689,19 @@ window.addEventListener('message', (event) => {
|
|
|
556
689
|
requestId: message.requestId || '',
|
|
557
690
|
});
|
|
558
691
|
})();
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (message.type === 'park-room') {
|
|
696
|
+
void (async () => {
|
|
697
|
+
await prepareRealtimeRoomDisconnect();
|
|
698
|
+
disconnectRealtimeRoom({ preserveEditorBindings: true });
|
|
699
|
+
})();
|
|
559
700
|
}
|
|
560
701
|
});
|
|
561
702
|
|
|
562
703
|
function scheduleSyncToRoom(elements, appState, files) {
|
|
563
|
-
if (!collabReady || suppressOnChange) {
|
|
704
|
+
if (!collabReady || !roomClient || suppressOnChange) {
|
|
564
705
|
return;
|
|
565
706
|
}
|
|
566
707
|
|
|
@@ -589,7 +730,7 @@ function initializeEditor(api) {
|
|
|
589
730
|
setFollowedSocket(null, { force: true });
|
|
590
731
|
}));
|
|
591
732
|
|
|
592
|
-
const sceneJson = pendingRemoteSceneJson || roomClient
|
|
733
|
+
const sceneJson = pendingRemoteSceneJson || roomClient?.getLastSceneJson?.() || '';
|
|
593
734
|
pendingRemoteSceneJson = '';
|
|
594
735
|
updateApiScene(parseSceneJson(sceneJson));
|
|
595
736
|
|
|
@@ -622,53 +763,15 @@ async function init() {
|
|
|
622
763
|
try {
|
|
623
764
|
applySurfaceTheme(currentTheme);
|
|
624
765
|
await ensureClientAuthenticated();
|
|
625
|
-
const initialScene = await
|
|
766
|
+
const initialScene = await connectDocumentClient(currentDocument.filePath);
|
|
767
|
+
if (!initialScene) {
|
|
768
|
+
throw new Error('Failed to connect initial Excalidraw document');
|
|
769
|
+
}
|
|
626
770
|
const initialData = sceneToInitialData(initialScene, { theme: currentTheme });
|
|
627
771
|
|
|
628
772
|
loadingElement?.remove();
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
onMount: handleEditorMount,
|
|
632
|
-
onInitialize: (api) => {
|
|
633
|
-
initializeEditor(api);
|
|
634
|
-
},
|
|
635
|
-
onUnmount: () => {
|
|
636
|
-
apiStateCleanupCallbacks.forEach((cleanup) => cleanup());
|
|
637
|
-
apiStateCleanupCallbacks = [];
|
|
638
|
-
excalidrawAPI = null;
|
|
639
|
-
collabReady = false;
|
|
640
|
-
},
|
|
641
|
-
initialData,
|
|
642
|
-
aiEnabled: false,
|
|
643
|
-
isCollaborating: true,
|
|
644
|
-
onChange: (elements, appState, files) => {
|
|
645
|
-
scheduleSyncToRoom(elements, appState, files);
|
|
646
|
-
roomClient.syncLocalSelectionAwareness(appState);
|
|
647
|
-
},
|
|
648
|
-
onPointerUpdate: (payload) => {
|
|
649
|
-
roomClient.scheduleLocalPointerAwareness(payload);
|
|
650
|
-
},
|
|
651
|
-
theme: currentTheme,
|
|
652
|
-
viewModeEnabled: isPreviewMode,
|
|
653
|
-
zenModeEnabled: isPreviewMode,
|
|
654
|
-
UIOptions: {
|
|
655
|
-
canvasActions: {
|
|
656
|
-
export: false,
|
|
657
|
-
loadScene: false,
|
|
658
|
-
saveToActiveFile: false,
|
|
659
|
-
toggleTheme: false,
|
|
660
|
-
},
|
|
661
|
-
},
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
const App = () => React.createElement(
|
|
665
|
-
'div',
|
|
666
|
-
{ style: { height: '100vh', width: '100%' } },
|
|
667
|
-
React.createElement(Excalidraw, excalidrawProps),
|
|
668
|
-
);
|
|
669
|
-
|
|
670
|
-
const root = createRoot(document.getElementById('root'));
|
|
671
|
-
root.render(React.createElement(App));
|
|
773
|
+
reactRoot = createRoot(document.getElementById('root'));
|
|
774
|
+
renderExcalidrawApp({ initialData });
|
|
672
775
|
} catch (error) {
|
|
673
776
|
console.error('[excalidraw] Failed to initialize:', error);
|
|
674
777
|
postToParent('error', {
|
|
@@ -156,7 +156,7 @@ function renderOidcPrompt(card, config, {
|
|
|
156
156
|
actions.className = 'auth-gate-actions';
|
|
157
157
|
|
|
158
158
|
const signInButton = document.createElement('button');
|
|
159
|
-
signInButton.className = 'auth-gate-button auth-gate-button--google';
|
|
159
|
+
signInButton.className = 'ui-button ui-button--secondary auth-gate-button auth-gate-button--google';
|
|
160
160
|
signInButton.type = 'button';
|
|
161
161
|
signInButton.append(createGoogleIcon(), document.createTextNode(config.submitLabel || 'Continue with Google'));
|
|
162
162
|
|
|
@@ -191,7 +191,7 @@ function renderStatusCard(card, {
|
|
|
191
191
|
|
|
192
192
|
if (secondaryActionLabel && typeof onSecondaryAction === 'function') {
|
|
193
193
|
const button = document.createElement('button');
|
|
194
|
-
button.className = 'auth-gate-secondary-button';
|
|
194
|
+
button.className = 'ui-button ui-button--secondary auth-gate-secondary-button';
|
|
195
195
|
button.type = 'button';
|
|
196
196
|
button.textContent = secondaryActionLabel;
|
|
197
197
|
button.addEventListener('click', () => {
|
|
@@ -220,7 +220,7 @@ function renderPasswordPrompt(card, config, {
|
|
|
220
220
|
label.textContent = config.passwordLabel;
|
|
221
221
|
|
|
222
222
|
const input = document.createElement('input');
|
|
223
|
-
input.className = 'auth-gate-input';
|
|
223
|
+
input.className = 'ui-input auth-gate-input';
|
|
224
224
|
input.type = 'password';
|
|
225
225
|
input.name = 'password';
|
|
226
226
|
input.autocomplete = 'current-password';
|
|
@@ -233,7 +233,7 @@ function renderPasswordPrompt(card, config, {
|
|
|
233
233
|
actions.className = 'auth-gate-actions';
|
|
234
234
|
|
|
235
235
|
const submitButton = document.createElement('button');
|
|
236
|
-
submitButton.className = 'auth-gate-button';
|
|
236
|
+
submitButton.className = 'ui-button ui-button--primary auth-gate-button';
|
|
237
237
|
submitButton.type = 'submit';
|
|
238
238
|
submitButton.textContent = config.submitLabel;
|
|
239
239
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { resolveApiUrl } from '../domain/runtime-paths.js';
|
|
2
|
+
|
|
3
|
+
export class BacklinksApiClient {
|
|
4
|
+
async readBacklinks(filePath, { signal } = {}) {
|
|
5
|
+
const response = await fetch(
|
|
6
|
+
resolveApiUrl(`/backlinks?file=${encodeURIComponent(filePath)}`),
|
|
7
|
+
{ signal },
|
|
8
|
+
);
|
|
9
|
+
const data = await response.json().catch(() => ({}));
|
|
10
|
+
if (!response.ok) {
|
|
11
|
+
throw new Error(data.error || 'Failed to load backlinks');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return Array.isArray(data.backlinks) ? data.backlinks : [];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const backlinksApiClient = new BacklinksApiClient();
|
|
@@ -93,6 +93,7 @@ export class EditorSession {
|
|
|
93
93
|
undoManager: this.pendingCollaborativeBindings.undoManager,
|
|
94
94
|
ytext: this.pendingCollaborativeBindings.ytext,
|
|
95
95
|
});
|
|
96
|
+
this.commentThreadStore.refreshComments();
|
|
96
97
|
this.pendingCollaborativeBindings = null;
|
|
97
98
|
this.bootstrapContent = null;
|
|
98
99
|
return true;
|
|
@@ -109,6 +110,7 @@ export class EditorSession {
|
|
|
109
110
|
content: this.bootstrapContent,
|
|
110
111
|
filePath: this.activeFilePath,
|
|
111
112
|
});
|
|
113
|
+
this.commentThreadStore.refreshComments();
|
|
112
114
|
return this.emitContentChange();
|
|
113
115
|
}
|
|
114
116
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { resolveApiUrl } from '../domain/runtime-paths.js';
|
|
2
|
+
|
|
3
|
+
function createRequestHeaders(requestId, headers = {}) {
|
|
4
|
+
const nextHeaders = { ...headers };
|
|
5
|
+
if (requestId) {
|
|
6
|
+
nextHeaders['X-CollabMD-Request-Id'] = String(requestId);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return nextHeaders;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function parseJsonResponse(response, fallbackError) {
|
|
13
|
+
const data = await response.json().catch(() => ({}));
|
|
14
|
+
if (!response.ok || data.ok === false) {
|
|
15
|
+
const error = new Error(data.error || fallbackError);
|
|
16
|
+
error.status = response.status;
|
|
17
|
+
error.body = data;
|
|
18
|
+
if (typeof data?.code === 'string') {
|
|
19
|
+
error.code = data.code;
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return data;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createSearchParams(values = {}) {
|
|
28
|
+
const params = new URLSearchParams();
|
|
29
|
+
Object.entries(values).forEach(([key, value]) => {
|
|
30
|
+
if (value == null || value === '') {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
params.set(key, String(value));
|
|
35
|
+
});
|
|
36
|
+
return params;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class GitApiClient {
|
|
40
|
+
async readStatus({ force = false } = {}) {
|
|
41
|
+
const response = await fetch(resolveApiUrl(`/git/status${force ? '?force=true' : ''}`));
|
|
42
|
+
return parseJsonResponse(response, 'Failed to load git status');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async readPullBackups() {
|
|
46
|
+
const response = await fetch(resolveApiUrl('/git/pull-backups'));
|
|
47
|
+
return parseJsonResponse(response, 'Failed to load pull backups');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async readHistory({ limit, offset } = {}) {
|
|
51
|
+
const params = createSearchParams({ limit, offset });
|
|
52
|
+
const response = await fetch(resolveApiUrl(`/git/history?${params.toString()}`));
|
|
53
|
+
return parseJsonResponse(response, 'Failed to load git history');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async readFileHistory({ path, limit, offset } = {}) {
|
|
57
|
+
const params = createSearchParams({ limit, offset, path });
|
|
58
|
+
const response = await fetch(resolveApiUrl(`/git/file-history?${params.toString()}`));
|
|
59
|
+
return parseJsonResponse(response, 'Failed to load file history');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async readDiff({
|
|
63
|
+
allowLargePatch = false,
|
|
64
|
+
metaOnly = false,
|
|
65
|
+
path = null,
|
|
66
|
+
scope = 'all',
|
|
67
|
+
} = {}) {
|
|
68
|
+
const params = createSearchParams({
|
|
69
|
+
allowLargePatch: allowLargePatch ? 'true' : null,
|
|
70
|
+
metaOnly: metaOnly ? 'true' : null,
|
|
71
|
+
path,
|
|
72
|
+
scope,
|
|
73
|
+
});
|
|
74
|
+
const response = await fetch(resolveApiUrl(`/git/diff?${params.toString()}`));
|
|
75
|
+
return parseJsonResponse(response, 'Failed to load git diff');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async readCommit({
|
|
79
|
+
allowLargePatch = false,
|
|
80
|
+
hash,
|
|
81
|
+
metaOnly = false,
|
|
82
|
+
path = null,
|
|
83
|
+
} = {}) {
|
|
84
|
+
const params = createSearchParams({
|
|
85
|
+
allowLargePatch: allowLargePatch ? 'true' : null,
|
|
86
|
+
hash,
|
|
87
|
+
metaOnly: metaOnly ? 'true' : null,
|
|
88
|
+
path,
|
|
89
|
+
});
|
|
90
|
+
const response = await fetch(resolveApiUrl(`/git/commit?${params.toString()}`));
|
|
91
|
+
return parseJsonResponse(response, 'Failed to load git commit');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async readFileSnapshot({ hash, path } = {}) {
|
|
95
|
+
const params = createSearchParams({ hash, path });
|
|
96
|
+
const response = await fetch(resolveApiUrl(`/git/file-snapshot?${params.toString()}`));
|
|
97
|
+
return parseJsonResponse(response, 'Failed to load historical file preview');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async stageFile({ path, requestId = null } = {}) {
|
|
101
|
+
return this.#postJson('/git/stage', { path }, { requestId, fallbackError: 'Failed to stage file' });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async unstageFile({ path, requestId = null } = {}) {
|
|
105
|
+
return this.#postJson('/git/unstage', { path }, { requestId, fallbackError: 'Failed to unstage file' });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async pushBranch({ requestId = null } = {}) {
|
|
109
|
+
return this.#postJson('/git/push', {}, { requestId, fallbackError: 'Failed to push branch' });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async pullBranch({ requestId = null } = {}) {
|
|
113
|
+
return this.#postJson('/git/pull', {}, { requestId, fallbackError: 'Failed to pull branch' });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async resetFile({ path, requestId = null } = {}) {
|
|
117
|
+
return this.#postJson('/git/reset-file', { path }, { requestId, fallbackError: 'Failed to reset file' });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async commit({ message, requestId = null } = {}) {
|
|
121
|
+
return this.#postJson('/git/commit', { message }, { requestId, fallbackError: 'Failed to commit staged changes' });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async #postJson(path, payload, { fallbackError, requestId = null } = {}) {
|
|
125
|
+
const response = await fetch(resolveApiUrl(path), {
|
|
126
|
+
body: JSON.stringify(payload),
|
|
127
|
+
headers: createRequestHeaders(requestId, { 'Content-Type': 'application/json' }),
|
|
128
|
+
method: 'POST',
|
|
129
|
+
});
|
|
130
|
+
return parseJsonResponse(response, fallbackError);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const gitApiClient = new GitApiClient();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { resolveApiUrl } from '../domain/runtime-paths.js';
|
|
2
|
+
|
|
3
|
+
export class PlantUmlApiClient {
|
|
4
|
+
async renderSvg(source) {
|
|
5
|
+
const response = await fetch(resolveApiUrl('/plantuml/render'), {
|
|
6
|
+
body: JSON.stringify({ source }),
|
|
7
|
+
headers: {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
},
|
|
10
|
+
method: 'POST',
|
|
11
|
+
});
|
|
12
|
+
const data = await response.json().catch(() => null);
|
|
13
|
+
if (!response.ok || !data?.ok || typeof data.svg !== 'string') {
|
|
14
|
+
throw new Error(data?.error || 'Failed to render PlantUML');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return data.svg;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const plantUmlApiClient = new PlantUmlApiClient();
|