collabmd 0.1.19 → 0.1.21
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 +13 -11
- package/docker-compose.yml +1 -1
- package/package.json +1 -1
- package/public/assets/css/style.css +1 -1
- package/public/assets/js/chunks/chunk-GFDM7YCF.js +1 -0
- package/public/assets/js/chunks/editor-session-EAUBJCHH.js +22 -0
- package/public/assets/js/chunks/{preview-render-compiler-UZ4SZDQQ.js → preview-render-compiler-6Y7TKXFJ.js} +1 -1
- package/public/assets/js/chunks/{quick-switcher-controller-J7I3CUET.js → quick-switcher-controller-A6SKH5HI.js} +1 -1
- package/public/assets/js/{excalidraw-editor-ZDYKMZOL.js → excalidraw-editor-TCIH6GJL.js} +1 -1
- package/public/assets/js/excalidraw-editor.js +1 -1
- package/public/assets/js/main.js +102 -75
- package/public/assets/js/preview-render-worker.js +15 -15
- package/public/index.html +14 -4
- package/src/client/application/app-shell/git-feature.js +86 -19
- package/src/client/application/app-shell/ui-feature.js +4 -0
- package/src/client/application/app-shell-elements.js +1 -0
- package/src/client/bootstrap/collabmd-app-shell.js +16 -0
- package/src/client/domain/vault-utils.js +2 -2
- package/src/client/excalidraw-editor.js +2 -1
- package/src/client/infrastructure/editor-session.js +4 -0
- package/src/client/infrastructure/editor-view-adapter.js +245 -7
- package/src/client/infrastructure/workspace-sync-client.js +324 -0
- package/src/client/presentation/backlinks-panel.js +157 -101
- package/src/client/presentation/comment-ui-controller.js +233 -10
- package/src/client/presentation/file-explorer-controller.js +15 -3
- package/src/client/presentation/file-explorer-view.js +152 -11
- package/src/client/presentation/git-panel-controller.js +73 -0
- package/src/client/presentation/outline-controller.js +25 -0
- package/src/client/styles/style.css +184 -9
- package/src/domain/wiki-link-resolver.js +16 -5
- package/src/domain/workspace-change.js +68 -0
- package/src/domain/workspace-room.js +3 -0
- package/src/server/create-app-server.js +41 -10
- package/src/server/domain/backlink-index.js +94 -1
- package/src/server/domain/collaboration/collaboration-room.js +191 -22
- package/src/server/domain/collaboration/room-registry.js +64 -10
- package/src/server/infrastructure/git/errors.js +4 -1
- package/src/server/infrastructure/git/git-service.js +215 -1
- package/src/server/infrastructure/git/responses.js +12 -19
- package/src/server/infrastructure/http/create-git-api-command-handler.js +58 -43
- package/src/server/infrastructure/http/create-git-api-handler.js +2 -0
- package/src/server/infrastructure/http/create-git-api-query-handler.js +16 -1
- package/src/server/infrastructure/http/create-request-handler.js +7 -0
- package/src/server/infrastructure/http/create-vault-api-command-handler.js +58 -14
- package/src/server/infrastructure/http/create-vault-api-handler.js +3 -0
- package/src/server/infrastructure/http/create-vault-api-query-handler.js +3 -1
- package/src/server/infrastructure/http/http-response.js +65 -28
- package/src/server/infrastructure/persistence/pull-backup-store.js +283 -0
- package/src/server/infrastructure/persistence/vault-file-store.js +179 -52
- package/src/server/infrastructure/workspace/file-system-sync-service.js +488 -0
- package/src/server/infrastructure/workspace/workspace-mutation-coordinator.js +551 -0
- package/public/assets/js/chunks/chunk-R3DDMJHH.js +0 -1
- package/public/assets/js/chunks/editor-session-AH6Z3MXW.js +0 -22
|
@@ -14,9 +14,27 @@ import { createWikiTargetIndex, resolveWikiTargetWithIndex } from '../../domain/
|
|
|
14
14
|
|
|
15
15
|
const WIKI_LINK_RE = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
|
|
16
16
|
|
|
17
|
+
function createDeferred() {
|
|
18
|
+
let resolve;
|
|
19
|
+
let reject;
|
|
20
|
+
const promise = new Promise((nextResolve, nextReject) => {
|
|
21
|
+
resolve = nextResolve;
|
|
22
|
+
reject = nextReject;
|
|
23
|
+
});
|
|
24
|
+
return { promise, reject, resolve };
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
export class BacklinkIndex {
|
|
18
|
-
constructor({
|
|
28
|
+
constructor({
|
|
29
|
+
rebuildDelayMs = 150,
|
|
30
|
+
setTimeoutFn = setTimeout,
|
|
31
|
+
clearTimeoutFn = clearTimeout,
|
|
32
|
+
vaultFileStore,
|
|
33
|
+
}) {
|
|
19
34
|
this.vaultFileStore = vaultFileStore;
|
|
35
|
+
this.rebuildDelayMs = rebuildDelayMs;
|
|
36
|
+
this.setTimeoutFn = setTimeoutFn;
|
|
37
|
+
this.clearTimeoutFn = clearTimeoutFn;
|
|
20
38
|
/** @type {Map<string, Set<string>>} sourcePath → set of resolved target paths */
|
|
21
39
|
this.forward = new Map();
|
|
22
40
|
/** @type {Map<string, Set<string>>} targetPath → set of source paths */
|
|
@@ -29,6 +47,11 @@ export class BacklinkIndex {
|
|
|
29
47
|
this._fileSet = new Set();
|
|
30
48
|
this._wikiTargetIndex = createWikiTargetIndex(this._fileList);
|
|
31
49
|
this._built = false;
|
|
50
|
+
this._requestedBuildVersion = 0;
|
|
51
|
+
this._completedBuildVersion = 0;
|
|
52
|
+
this._buildPromise = null;
|
|
53
|
+
this._scheduledBuildTimer = null;
|
|
54
|
+
this._scheduledBuildDeferred = null;
|
|
32
55
|
}
|
|
33
56
|
|
|
34
57
|
/**
|
|
@@ -36,6 +59,74 @@ export class BacklinkIndex {
|
|
|
36
59
|
* Called once at server startup.
|
|
37
60
|
*/
|
|
38
61
|
async build() {
|
|
62
|
+
this._requestedBuildVersion += 1;
|
|
63
|
+
return this.flushScheduledBuild();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
scheduleBuild({ delayMs = this.rebuildDelayMs } = {}) {
|
|
67
|
+
this._requestedBuildVersion += 1;
|
|
68
|
+
if (!this._scheduledBuildDeferred) {
|
|
69
|
+
this._scheduledBuildDeferred = createDeferred();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (this._scheduledBuildTimer) {
|
|
73
|
+
this.clearTimeoutFn(this._scheduledBuildTimer);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this._scheduledBuildTimer = this.setTimeoutFn(() => {
|
|
77
|
+
this._scheduledBuildTimer = null;
|
|
78
|
+
const deferred = this._scheduledBuildDeferred;
|
|
79
|
+
this._scheduledBuildDeferred = null;
|
|
80
|
+
this._ensureBuiltToRequestedVersion()
|
|
81
|
+
.then(() => deferred?.resolve())
|
|
82
|
+
.catch((error) => deferred?.reject(error));
|
|
83
|
+
}, delayMs);
|
|
84
|
+
this._scheduledBuildTimer.unref?.();
|
|
85
|
+
|
|
86
|
+
return this._scheduledBuildDeferred.promise;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async flushScheduledBuild() {
|
|
90
|
+
if (this._scheduledBuildTimer) {
|
|
91
|
+
this.clearTimeoutFn(this._scheduledBuildTimer);
|
|
92
|
+
this._scheduledBuildTimer = null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const deferred = this._scheduledBuildDeferred;
|
|
96
|
+
this._scheduledBuildDeferred = null;
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await this._ensureBuiltToRequestedVersion();
|
|
100
|
+
deferred?.resolve();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
deferred?.reject(error);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async _ensureBuiltToRequestedVersion() {
|
|
108
|
+
while (this._completedBuildVersion < this._requestedBuildVersion) {
|
|
109
|
+
await this._runSingleBuild();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async _runSingleBuild() {
|
|
114
|
+
if (this._buildPromise) {
|
|
115
|
+
return this._buildPromise;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const targetVersion = this._requestedBuildVersion;
|
|
119
|
+
this._buildPromise = (async () => {
|
|
120
|
+
await this._performBuild();
|
|
121
|
+
this._completedBuildVersion = Math.max(this._completedBuildVersion, targetVersion);
|
|
122
|
+
})().finally(() => {
|
|
123
|
+
this._buildPromise = null;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return this._buildPromise;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async _performBuild() {
|
|
39
130
|
this.forward.clear();
|
|
40
131
|
this.reverse.clear();
|
|
41
132
|
this.contextsBySource.clear();
|
|
@@ -169,6 +260,8 @@ export class BacklinkIndex {
|
|
|
169
260
|
* Returns: [{ file: string, contexts: string[] }]
|
|
170
261
|
*/
|
|
171
262
|
async getBacklinks(filePath) {
|
|
263
|
+
await this.flushScheduledBuild();
|
|
264
|
+
|
|
172
265
|
const sources = this.reverse.get(filePath);
|
|
173
266
|
if (!sources || sources.size === 0) {
|
|
174
267
|
return [];
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
serializeExcalidrawRoomScene,
|
|
18
18
|
tryParseExcalidrawSceneJson,
|
|
19
19
|
} from '../../../domain/excalidraw-room-codec.js';
|
|
20
|
+
import { normalizeWorkspaceEvent } from '../../../domain/workspace-change.js';
|
|
21
|
+
import { WORKSPACE_EVENT_MAX_MESSAGES, WORKSPACE_ROOM_NAME } from '../../../domain/workspace-room.js';
|
|
20
22
|
|
|
21
23
|
function closeSlowClient(ws, clientState, { maxBufferedAmountBytes, name }) {
|
|
22
24
|
if (!clientState || clientState.backpressureCloseIssued) {
|
|
@@ -86,6 +88,41 @@ function isExcalidrawRoom(name) {
|
|
|
86
88
|
return typeof name === 'string' && name.endsWith('.excalidraw');
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
function isWorkspaceRoom(name) {
|
|
92
|
+
return name === WORKSPACE_ROOM_NAME;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function computeTextReplacement(currentContent, nextContent) {
|
|
96
|
+
const currentText = String(currentContent ?? '');
|
|
97
|
+
const nextText = String(nextContent ?? '');
|
|
98
|
+
if (currentText === nextText) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let prefixLength = 0;
|
|
103
|
+
const maxPrefix = Math.min(currentText.length, nextText.length);
|
|
104
|
+
while (prefixLength < maxPrefix && currentText[prefixLength] === nextText[prefixLength]) {
|
|
105
|
+
prefixLength += 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let currentSuffixLength = currentText.length;
|
|
109
|
+
let nextSuffixLength = nextText.length;
|
|
110
|
+
while (
|
|
111
|
+
currentSuffixLength > prefixLength
|
|
112
|
+
&& nextSuffixLength > prefixLength
|
|
113
|
+
&& currentText[currentSuffixLength - 1] === nextText[nextSuffixLength - 1]
|
|
114
|
+
) {
|
|
115
|
+
currentSuffixLength -= 1;
|
|
116
|
+
nextSuffixLength -= 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
deleteCount: currentSuffixLength - prefixLength,
|
|
121
|
+
insertText: nextText.slice(prefixLength, nextSuffixLength),
|
|
122
|
+
start: prefixLength,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
89
126
|
export class CollaborationRoom {
|
|
90
127
|
constructor({
|
|
91
128
|
documentStore = null,
|
|
@@ -332,36 +369,75 @@ export class CollaborationRoom {
|
|
|
332
369
|
return false;
|
|
333
370
|
}
|
|
334
371
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
372
|
+
return this.applyExternalContent(content, {
|
|
373
|
+
commentThreads,
|
|
374
|
+
replaceCommentThreads: true,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async applyExternalContent(content, {
|
|
379
|
+
commentThreads = [],
|
|
380
|
+
replaceCommentThreads = false,
|
|
381
|
+
} = {}) {
|
|
382
|
+
if (this.deleted || this.destroyed) {
|
|
383
|
+
return { ok: false, reason: 'room-unavailable' };
|
|
342
384
|
}
|
|
343
385
|
|
|
344
|
-
this.doc.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
386
|
+
const comments = this.doc.getArray('comments');
|
|
387
|
+
if (isExcalidrawRoom(this.name)) {
|
|
388
|
+
const parsedExcalidrawScene = tryParseExcalidrawSceneJson(content);
|
|
389
|
+
if (!parsedExcalidrawScene) {
|
|
390
|
+
return { ok: false, reason: 'invalid-excalidraw' };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
this.doc.transact(() => {
|
|
349
394
|
replaceExcalidrawRoomScene(this.doc, parsedExcalidrawScene);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
ytext.insert(0, content);
|
|
395
|
+
if (replaceCommentThreads) {
|
|
396
|
+
if (comments.length > 0) {
|
|
397
|
+
comments.delete(0, comments.length);
|
|
398
|
+
}
|
|
399
|
+
populateCommentThreads(comments, commentThreads);
|
|
356
400
|
}
|
|
401
|
+
}, 'workspace-reconcile');
|
|
402
|
+
|
|
403
|
+
return { ok: true, highlightRange: null };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const ytext = this.doc.getText('codemirror');
|
|
407
|
+
const replacement = computeTextReplacement(ytext.toString(), content);
|
|
408
|
+
if (!replacement && !replaceCommentThreads) {
|
|
409
|
+
return { highlightRange: null, ok: true, skipped: true };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
this.doc.transact(() => {
|
|
413
|
+
if (replacement?.deleteCount) {
|
|
414
|
+
ytext.delete(replacement.start, replacement.deleteCount);
|
|
357
415
|
}
|
|
358
|
-
if (
|
|
359
|
-
|
|
416
|
+
if (replacement?.insertText) {
|
|
417
|
+
ytext.insert(replacement.start, replacement.insertText);
|
|
418
|
+
}
|
|
419
|
+
if (replaceCommentThreads) {
|
|
420
|
+
if (comments.length > 0) {
|
|
421
|
+
comments.delete(0, comments.length);
|
|
422
|
+
}
|
|
423
|
+
populateCommentThreads(comments, commentThreads);
|
|
360
424
|
}
|
|
361
|
-
populateCommentThreads(comments, commentThreads);
|
|
362
425
|
}, 'workspace-reconcile');
|
|
363
426
|
|
|
364
|
-
return
|
|
427
|
+
return {
|
|
428
|
+
highlightRange: replacement
|
|
429
|
+
? {
|
|
430
|
+
from: replacement.start,
|
|
431
|
+
to: replacement.start + replacement.insertText.length,
|
|
432
|
+
}
|
|
433
|
+
: null,
|
|
434
|
+
ok: true,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
applyExternalDeletion() {
|
|
439
|
+
this.markDeleted();
|
|
440
|
+
return this.destroy();
|
|
365
441
|
}
|
|
366
442
|
|
|
367
443
|
rename(nextName) {
|
|
@@ -537,6 +613,10 @@ export class CollaborationRoom {
|
|
|
537
613
|
}
|
|
538
614
|
|
|
539
615
|
getPersistedContent() {
|
|
616
|
+
if (isWorkspaceRoom(this.name)) {
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
|
|
540
620
|
if (!isExcalidrawRoom(this.name)) {
|
|
541
621
|
return this.doc.getText('codemirror').toString();
|
|
542
622
|
}
|
|
@@ -600,4 +680,93 @@ export class CollaborationRoom {
|
|
|
600
680
|
console.error(`[room:${this.name}] Failed to handle message:`, error.message);
|
|
601
681
|
}
|
|
602
682
|
}
|
|
683
|
+
|
|
684
|
+
replaceWorkspaceEntries(entries = new Map(), {
|
|
685
|
+
generatedAt = Date.now(),
|
|
686
|
+
} = {}) {
|
|
687
|
+
if (!isWorkspaceRoom(this.name)) {
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const normalizedEntries = entries instanceof Map ? entries : new Map(entries);
|
|
692
|
+
const entriesMap = this.doc.getMap('entries');
|
|
693
|
+
const metaMap = this.doc.getMap('meta');
|
|
694
|
+
|
|
695
|
+
this.doc.transact(() => {
|
|
696
|
+
Array.from(entriesMap.keys()).forEach((key) => {
|
|
697
|
+
if (!normalizedEntries.has(key)) {
|
|
698
|
+
entriesMap.delete(key);
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
normalizedEntries.forEach((entry, pathValue) => {
|
|
703
|
+
entriesMap.set(pathValue, entry);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
metaMap.set('lastSnapshotAt', generatedAt);
|
|
707
|
+
metaMap.set('revision', Number(metaMap.get('revision') || 0) + 1);
|
|
708
|
+
}, 'workspace-room-entries');
|
|
709
|
+
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
applyWorkspaceEntryPatch({
|
|
714
|
+
deletes = [],
|
|
715
|
+
upserts = new Map(),
|
|
716
|
+
} = {}, {
|
|
717
|
+
generatedAt = Date.now(),
|
|
718
|
+
} = {}) {
|
|
719
|
+
if (!isWorkspaceRoom(this.name)) {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const normalizedUpserts = upserts instanceof Map ? upserts : new Map(upserts);
|
|
724
|
+
const normalizedDeletes = Array.from(new Set((deletes ?? []).filter(Boolean)));
|
|
725
|
+
if (normalizedUpserts.size === 0 && normalizedDeletes.length === 0) {
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const entriesMap = this.doc.getMap('entries');
|
|
730
|
+
const metaMap = this.doc.getMap('meta');
|
|
731
|
+
|
|
732
|
+
this.doc.transact(() => {
|
|
733
|
+
normalizedDeletes.forEach((pathValue) => {
|
|
734
|
+
entriesMap.delete(pathValue);
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
normalizedUpserts.forEach((entry, pathValue) => {
|
|
738
|
+
entriesMap.set(pathValue, entry);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
metaMap.set('lastSnapshotAt', generatedAt);
|
|
742
|
+
metaMap.set('revision', Number(metaMap.get('revision') || 0) + 1);
|
|
743
|
+
}, 'workspace-room-entry-patch');
|
|
744
|
+
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
publishWorkspaceEvent(event) {
|
|
749
|
+
if (!isWorkspaceRoom(this.name)) {
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const normalizedEvent = normalizeWorkspaceEvent(event);
|
|
754
|
+
if (!normalizedEvent) {
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const events = this.doc.getArray('events');
|
|
759
|
+
const metaMap = this.doc.getMap('meta');
|
|
760
|
+
this.doc.transact(() => {
|
|
761
|
+
events.push([normalizedEvent]);
|
|
762
|
+
const overflow = events.length - WORKSPACE_EVENT_MAX_MESSAGES;
|
|
763
|
+
if (overflow > 0) {
|
|
764
|
+
events.delete(0, overflow);
|
|
765
|
+
}
|
|
766
|
+
metaMap.set('lastEventAt', normalizedEvent.createdAt);
|
|
767
|
+
metaMap.set('revision', Number(metaMap.get('revision') || 0) + 1);
|
|
768
|
+
}, 'workspace-room-event');
|
|
769
|
+
|
|
770
|
+
return normalizedEvent;
|
|
771
|
+
}
|
|
603
772
|
}
|
|
@@ -57,27 +57,67 @@ export class RoomRegistry {
|
|
|
57
57
|
this.rooms.clear();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
getRooms() {
|
|
61
|
+
return Array.from(this.rooms.entries());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async reloadAllFromDisk() {
|
|
65
|
+
await Promise.allSettled(
|
|
66
|
+
Array.from(this.rooms.values(), (room) => room.reloadFromDisk?.()),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
async reconcileWorkspaceChange(workspaceChange = {}) {
|
|
61
71
|
const deletedPaths = new Set(workspaceChange.deletedPaths ?? []);
|
|
62
72
|
const renamedPaths = Array.isArray(workspaceChange.renamedPaths) ? workspaceChange.renamedPaths : [];
|
|
63
|
-
const
|
|
73
|
+
const pendingDeletes = [];
|
|
74
|
+
const highlightRanges = [];
|
|
75
|
+
const reloadRequiredPaths = [];
|
|
64
76
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
77
|
+
renamedPaths.forEach((entry) => {
|
|
78
|
+
if (!entry?.oldPath || !entry?.newPath) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
71
81
|
|
|
82
|
+
if (this.rename(entry.oldPath, entry.newPath)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const room = this.rooms.get(entry.oldPath);
|
|
87
|
+
if (room) {
|
|
88
|
+
pendingDeletes.push([entry.oldPath, room]);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
deletedPaths.forEach((pathValue) => {
|
|
93
|
+
if (!pathValue) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const room = this.rooms.get(pathValue);
|
|
98
|
+
if (room) {
|
|
99
|
+
pendingDeletes.push([pathValue, room]);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await Promise.allSettled(
|
|
104
|
+
pendingDeletes.map(async ([pathValue, room]) => {
|
|
72
105
|
room.markDeleted?.();
|
|
73
|
-
|
|
106
|
+
if (typeof room.applyExternalDeletion === 'function') {
|
|
107
|
+
await room.applyExternalDeletion();
|
|
108
|
+
} else {
|
|
109
|
+
await room.destroy?.();
|
|
110
|
+
}
|
|
74
111
|
if (this.rooms.get(pathValue) === room) {
|
|
75
112
|
this.rooms.delete(pathValue);
|
|
76
113
|
}
|
|
77
114
|
}),
|
|
78
115
|
);
|
|
79
116
|
|
|
80
|
-
const blockedPaths = new Set([
|
|
117
|
+
const blockedPaths = new Set([
|
|
118
|
+
...deletedPaths,
|
|
119
|
+
...renamedPaths.flatMap((entry) => [entry?.oldPath, entry?.newPath]),
|
|
120
|
+
]);
|
|
81
121
|
await Promise.allSettled(
|
|
82
122
|
Array.from(new Set(workspaceChange.changedPaths ?? []))
|
|
83
123
|
.filter((pathValue) => pathValue && !blockedPaths.has(pathValue))
|
|
@@ -87,8 +127,22 @@ export class RoomRegistry {
|
|
|
87
127
|
return;
|
|
88
128
|
}
|
|
89
129
|
|
|
90
|
-
await room.reloadFromDisk?.();
|
|
130
|
+
const result = await room.reloadFromDisk?.();
|
|
131
|
+
if (result && result.ok === false && result.reason === 'invalid-excalidraw') {
|
|
132
|
+
reloadRequiredPaths.push(pathValue);
|
|
133
|
+
} else if (result?.highlightRange) {
|
|
134
|
+
highlightRanges.push({
|
|
135
|
+
from: result.highlightRange.from,
|
|
136
|
+
path: pathValue,
|
|
137
|
+
to: result.highlightRange.to,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
91
140
|
}),
|
|
92
141
|
);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
highlightRanges,
|
|
145
|
+
reloadRequiredPaths,
|
|
146
|
+
};
|
|
93
147
|
}
|
|
94
148
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export function createGitRequestError(statusCode, message) {
|
|
1
|
+
export function createGitRequestError(statusCode, message, requestCode = null) {
|
|
2
2
|
const error = new Error(message);
|
|
3
3
|
error.statusCode = statusCode;
|
|
4
|
+
if (requestCode) {
|
|
5
|
+
error.requestCode = requestCode;
|
|
6
|
+
}
|
|
4
7
|
return error;
|
|
5
8
|
}
|