collabmd 0.1.45 → 0.1.46
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 +39 -19
- package/bin/collabmd.js +27 -1
- package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-CYM5nxUi.js +1 -0
- package/dist/client/assets/browser-utils--H_DkyEF.js +1 -0
- package/dist/client/assets/chunk-ERSWUTIN-hHdvnhaY.js +41 -0
- package/dist/client/assets/comment-thread-store-CdcwrObc.js +1 -0
- package/dist/client/assets/comment-threads-9J6Em-_y.js +4 -0
- package/dist/client/assets/comment-ui-shared-BC4pt4w8.js +7 -0
- package/dist/client/assets/dist-B5HTH2BO.js +1 -0
- package/dist/client/assets/{dist-DhLvC8DS.js → dist-B5Jf1SLZ.js} +2 -2
- package/dist/client/assets/{dist-CnL7Y2sM.js → dist-BDt3rz7l.js} +1 -1
- package/dist/client/assets/dist-BEwK4csK.js +1 -0
- package/dist/client/assets/dist-BKW4PALF.js +1 -0
- package/dist/client/assets/dist-BWcHHOx-.js +1 -0
- package/dist/client/assets/{dist-CYZmbokb.js → dist-BalScOwZ.js} +9 -9
- package/dist/client/assets/dist-Bf8f4HmM.js +6 -0
- package/dist/client/assets/dist-BjHJDtyU.js +1 -0
- package/dist/client/assets/dist-Bm6pf9Bl.js +1 -0
- package/dist/client/assets/dist-BoVBdSjk.js +1 -0
- package/dist/client/assets/dist-CC5IrQMG.js +1 -0
- package/dist/client/assets/{dist-C11hHqv_.js → dist-CCP_AKPG.js} +12 -12
- package/dist/client/assets/dist-CZgOmxK6.js +1 -0
- package/dist/client/assets/{dist-D2AQ-76g.js → dist-Cqhv5LAk.js} +1 -1
- package/dist/client/assets/{dist-MvcV4190.js → dist-D-Z6FLem.js} +1 -1
- package/dist/client/assets/dist-DLMiIsNC.js +11 -0
- package/dist/client/assets/{dist-3bgCp7iY.js → dist-DZKu9Cs8.js} +1 -1
- package/dist/client/assets/dist-DaYdhcVs.js +1 -0
- package/dist/client/assets/dist-FWJsgGYI.js +1 -0
- package/dist/client/assets/dist-Kh2COra_.js +1 -0
- package/dist/client/assets/{dist-JYEDRrPT.js → dist-PDZ3bSqm.js} +1 -1
- package/dist/client/assets/{dist-Drp8uu5J.js → dist-TjMh8n1u.js} +1 -1
- package/dist/client/assets/{dist-DGb27Dl6.js → dist-dtAdgL39.js} +1 -1
- package/dist/client/assets/dist-jv4WdPnK.js +9 -0
- package/dist/client/assets/{dist-DK5Gs2wa.js → dist-rGBmqxC7.js} +1 -1
- package/dist/client/assets/{dist-DCzuX1AF.js → dist-shrl6K_0.js} +1 -1
- package/dist/client/assets/dist-tOaegDYS2.js +1 -0
- package/dist/client/assets/dist-ule2n6ak.js +1 -0
- package/dist/client/assets/dist-xElaxbia.js +1 -0
- package/dist/client/assets/{drawio-editor-ClE8jpoG.js → drawio-editor-FzY1cVw8.js} +1 -1
- package/dist/client/assets/drawioEditor-BjiKJqlc.js +2 -0
- package/dist/client/assets/editor-session-Cm9ekeNo.js +54 -0
- package/dist/client/assets/embedded-editor-base-CwTkk911.css +1 -0
- package/dist/client/assets/{preview-render-profile-BZzGSg6K.js → es-DXGV3RHd.js} +13 -13
- package/dist/client/assets/excalidraw-editor-B46zsTFI.js +9 -0
- package/dist/client/assets/{excalidraw-editor-CyGwOhLz.css → excalidraw-editor-Bt7gMSoh.css} +1 -1
- package/dist/client/assets/excalidraw-scene-BOben2gr.js +1 -0
- package/dist/client/assets/excalidrawEditor-CT2pHq7e.js +2 -0
- package/dist/client/assets/{export-page-CUfws2T-.js → export-page-A7NHawJY.js} +3 -3
- package/dist/client/assets/exportDocument-B2L8fe34.css +1 -0
- package/dist/client/assets/exportDocument-BaqcOAAW.js +2 -0
- package/dist/client/assets/file-icon-svg-CQAjlWjK.js +1 -0
- package/dist/client/assets/file-kind-BbqgGzpc.js +1 -0
- package/dist/client/assets/image-QXRYHUNP-SWEV8H0J.js +1 -0
- package/dist/client/assets/index-ClbjMBVt.js +2 -0
- package/dist/client/assets/index-Pum8ET-5.css +1 -0
- package/dist/client/assets/main-B3uPyqic.js +893 -0
- package/dist/client/assets/pdf-BL9Pr2D5.js +54 -0
- package/dist/client/assets/pdf.worker-CLesOks4.mjs +64857 -0
- package/dist/client/assets/pdf_viewer-FVkkThnz.js +551 -0
- package/dist/client/assets/{preview-render-compiler-BSaQO_p3.js → preview-render-compiler-BD-XhkS2.js} +2 -2
- package/dist/client/assets/preview-render-compiler-BKedaMxx.js +1 -0
- package/dist/client/assets/preview-render-profile-CE-5rVTC.js +1 -0
- package/dist/client/assets/{preview-render-worker-B42LWvrD.js → preview-render-worker-CiPqc9n6.js} +20 -20
- package/dist/client/assets/prod-Dy20X7eT.js +1 -0
- package/dist/client/assets/prod-hnwh7wRM.js +177 -0
- package/dist/client/assets/quick-switcher-controller-fqOYR7qR.js +12 -0
- package/dist/client/assets/runtime-config-BLrEKVO1.js +1 -0
- package/dist/client/assets/vault-api-client-_LpV1aIn.js +6 -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 +15 -3
- package/docker-compose.yml +42 -0
- package/package.json +17 -4
- package/scripts/local-structurizr-compose.mjs +101 -0
- package/src/client/app/index.html +13 -1
- package/src/client/application/app-shell/comments-feature.js +19 -10
- package/src/client/application/app-shell/git-feature.js +12 -0
- package/src/client/application/app-shell/presence-feature.js +2 -1
- package/src/client/application/app-shell/ui-feature-shell.js +45 -2
- package/src/client/application/app-shell/ui-feature-toolbar.js +2 -52
- package/src/client/application/app-shell/workspace-feature.js +5 -1
- package/src/client/application/app-shell-elements.js +2 -0
- package/src/client/application/pdf-preview-controller.js +992 -0
- package/src/client/application/structurizr-preview-controller.js +395 -0
- package/src/client/application/workspace-coordinator.js +25 -3
- package/src/client/application/workspace-preview-controller.js +91 -2
- package/src/client/bootstrap/collabmd-app-shell.js +31 -5
- package/src/client/browser-utils.js +44 -0
- package/src/client/domain/runtime-paths.js +26 -5
- package/src/client/domain/structurizr-language.js +99 -0
- package/src/client/domain/vault-paths.js +48 -0
- package/src/client/excalidraw-editor.js +833 -51
- package/src/client/infrastructure/browser-preferences-port.js +10 -0
- package/src/client/infrastructure/comment-thread-store.js +34 -13
- package/src/client/infrastructure/editor-session.js +10 -0
- package/src/client/infrastructure/editor-view-adapter.js +46 -2
- package/src/client/infrastructure/excalidraw-room-client.js +103 -14
- package/src/client/infrastructure/git-api-client.js +5 -0
- package/src/client/infrastructure/vault-api-client.js +26 -0
- package/src/client/presentation/comment-ui/comment-ui-shared.js +4 -0
- package/src/client/presentation/drawio-embed-controller.js +9 -102
- package/src/client/presentation/drawio-viewer.js +118 -0
- package/src/client/presentation/excalidraw-embed-controller.js +74 -8
- package/src/client/presentation/file-action-controller.js +102 -0
- package/src/client/presentation/file-explorer-view.js +10 -29
- package/src/client/presentation/file-icon-svg.js +40 -0
- package/src/client/presentation/file-tree-state.js +2 -0
- package/src/client/presentation/git-diff-view-controller.js +731 -3
- package/src/client/presentation/outline-controller.js +86 -0
- package/src/client/presentation/quick-switcher-controller.js +25 -13
- package/src/client/presentation/quick-switcher-text-search.js +1 -1
- package/src/client/styles/components/navigation.css +11 -1
- package/src/client/styles/features/diagram-preview.css +76 -0
- package/src/client/styles/features/diff-special.css +309 -0
- package/src/client/styles/foundation/themes.css +6 -0
- package/src/client/styles/layout/shell.css +358 -1
- package/src/client/styles/layout/view-modes.css +1 -0
- package/src/client/styles/style.css +1 -0
- package/src/client/styles/surfaces/excalidraw-editor.css +294 -0
- package/src/domain/comment-threads.js +63 -5
- package/src/domain/excalidraw-room-codec.js +6 -4
- package/src/domain/file-kind.js +35 -3
- package/src/server/config/env.js +79 -0
- package/src/server/create-app-server.js +12 -0
- package/src/server/domain/bases/base-definition.js +1 -1
- package/src/server/domain/bases/base-expression-runtime.js +1 -4
- package/src/server/domain/bases/base-index-snapshot-store.js +1 -4
- package/src/server/domain/bases/base-transform.js +1 -4
- package/src/server/domain/collaboration/collaboration-room.js +55 -21
- package/src/server/domain/comment-overview.js +15 -5
- package/src/server/domain/ripgrep-search-service.js +4 -0
- package/src/server/infrastructure/git/diff-service.js +5 -0
- package/src/server/infrastructure/git/git-service.js +21 -0
- package/src/server/infrastructure/git/history-service.js +54 -0
- package/src/server/infrastructure/git/image-mime.js +14 -0
- package/src/server/infrastructure/git/untracked-files.js +28 -1
- package/src/server/infrastructure/http/create-git-api-query-handler.js +31 -1
- package/src/server/infrastructure/http/create-request-handler.js +16 -23
- package/src/server/infrastructure/http/create-static-handler.js +5 -2
- package/src/server/infrastructure/http/create-structurizr-api-handler.js +145 -0
- package/src/server/infrastructure/http/create-vault-api-command-handler.js +66 -4
- package/src/server/infrastructure/http/create-vault-api-query-handler.js +51 -3
- package/src/server/infrastructure/http/http-request-helpers.js +22 -0
- package/src/server/infrastructure/persistence/path-utils.js +1 -1
- package/src/server/infrastructure/persistence/vault-file-store.js +3 -0
- package/src/server/infrastructure/structurizr/structurizr-embed.css +43 -0
- package/src/server/infrastructure/structurizr/structurizr-workspace-service.js +374 -0
- package/src/server/infrastructure/websocket/attach-collaboration-gateway.js +4 -22
- package/dist/client/assets/CodeMirrorEditor-UCMRAMCV-CbSISC1w.js +0 -1
- package/dist/client/assets/browser-utils-CKtI_rZl.js +0 -1
- package/dist/client/assets/chunk-ERSWUTIN-ME9RVNel.js +0 -41
- package/dist/client/assets/comment-threads-DDegmaFO.js +0 -4
- package/dist/client/assets/dist-BH87JoHx.js +0 -11
- package/dist/client/assets/dist-BIkPRZO7.js +0 -6
- package/dist/client/assets/dist-BqCsFpx4.js +0 -1
- package/dist/client/assets/dist-C2JHCt8U.js +0 -1
- package/dist/client/assets/dist-CZjTtmTu.js +0 -1
- package/dist/client/assets/dist-Cig4Ov_X.js +0 -1
- package/dist/client/assets/dist-CqQEC8gR.js +0 -1
- package/dist/client/assets/dist-D4Rzqod1.js +0 -1
- package/dist/client/assets/dist-D5sn-mwk.js +0 -9
- package/dist/client/assets/dist-D7G-n87v.js +0 -1
- package/dist/client/assets/dist-DRqOrNgN.js +0 -1
- package/dist/client/assets/dist-DetkMS_4.js +0 -1
- package/dist/client/assets/dist-Dp6FVkQ1.js +0 -1
- package/dist/client/assets/dist-DtbjNB28.js +0 -1
- package/dist/client/assets/dist-DyrYcvpo2.js +0 -1
- package/dist/client/assets/dist-GrygD2zE.js +0 -1
- package/dist/client/assets/dist-HmEpWUow.js +0 -1
- package/dist/client/assets/dist-RTa81Km7.js +0 -1
- package/dist/client/assets/drawioEditor-CXaTM9Ds.js +0 -2
- package/dist/client/assets/editor-session-GTwZS6Df.js +0 -16
- package/dist/client/assets/embedded-editor-base-CiNEc8vy.css +0 -1
- package/dist/client/assets/excalidraw-editor-BZVM81BO.js +0 -9
- package/dist/client/assets/excalidraw-scene-C8n9CHsK.js +0 -176
- package/dist/client/assets/excalidrawEditor-DxJanCze.js +0 -2
- package/dist/client/assets/exportDocument-BCuxqXDY.js +0 -2
- package/dist/client/assets/exportDocument-DV-OX74E.css +0 -1
- package/dist/client/assets/file-kind-BKPOIRPE.js +0 -1
- package/dist/client/assets/image-QXRYHUNP-lfTHE3bb.js +0 -1
- package/dist/client/assets/index-C3YnUgMD.css +0 -1
- package/dist/client/assets/index-D2wXraJ1.js +0 -2
- package/dist/client/assets/main-DtiGz7uI.js +0 -795
- package/dist/client/assets/preview-render-compiler-csGM5ZFz.js +0 -1
- package/dist/client/assets/quick-switcher-controller-shW_6vPY.js +0 -12
- package/dist/client/assets/room-CQLoqtMo.js +0 -1
- package/dist/client/assets/runtime-config-DSGmGE9V.js +0 -1
- package/dist/client/assets/vault-api-client-C6bcGwy-.js +0 -5
- package/src/server/domain/collaboration/room-persistence-controller.js +0 -80
|
@@ -5,7 +5,7 @@ export const COMMENT_EXCERPT_MAX_LENGTH = 160;
|
|
|
5
5
|
export const COMMENT_ANCHOR_QUOTE_MAX_LENGTH = 280;
|
|
6
6
|
export const COMMENT_REACTION_EMOJI_MAX_LENGTH = 16;
|
|
7
7
|
|
|
8
|
-
const COMMENT_ANCHOR_KINDS = new Set(['line', 'text']);
|
|
8
|
+
const COMMENT_ANCHOR_KINDS = new Set(['diagram-element', 'line', 'text']);
|
|
9
9
|
|
|
10
10
|
function asFiniteNumber(value) {
|
|
11
11
|
return Number.isFinite(value) ? value : null;
|
|
@@ -55,6 +55,38 @@ function normalizeAnchorKind(value) {
|
|
|
55
55
|
return COMMENT_ANCHOR_KINDS.has(value) ? value : null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function normalizeDiagramElementId(value) {
|
|
59
|
+
const normalized = asString(value).trim().slice(0, 240);
|
|
60
|
+
return normalized || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeDiagramAnchorPoint(value) {
|
|
64
|
+
const point = asObject(value);
|
|
65
|
+
const x = asFiniteNumber(point?.x);
|
|
66
|
+
const y = asFiniteNumber(point?.y);
|
|
67
|
+
return x === null || y === null ? null : { x, y };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeDiagramAnchorSnapshot(value) {
|
|
71
|
+
const snapshot = asObject(value);
|
|
72
|
+
const x = asFiniteNumber(snapshot?.x);
|
|
73
|
+
const y = asFiniteNumber(snapshot?.y);
|
|
74
|
+
const width = asFiniteNumber(snapshot?.width);
|
|
75
|
+
const height = asFiniteNumber(snapshot?.height);
|
|
76
|
+
if (x === null || y === null || width === null || height === null) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
height,
|
|
82
|
+
text: normalizeCommentQuote(snapshot?.text),
|
|
83
|
+
type: asString(snapshot?.type).trim() || 'element',
|
|
84
|
+
width,
|
|
85
|
+
x,
|
|
86
|
+
y,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
58
90
|
export function normalizeCommentBody(value) {
|
|
59
91
|
const normalized = String(value ?? '')
|
|
60
92
|
.replace(/\r\n?/g, '\n')
|
|
@@ -196,6 +228,23 @@ export function createCommentId(prefix = 'comment') {
|
|
|
196
228
|
|
|
197
229
|
export function normalizeCommentAnchor(record = {}) {
|
|
198
230
|
const anchorKind = normalizeAnchorKind(record.anchorKind);
|
|
231
|
+
if (anchorKind === 'diagram-element') {
|
|
232
|
+
const anchorPoint = normalizeDiagramAnchorPoint(record.anchorPoint);
|
|
233
|
+
const anchorSnapshot = normalizeDiagramAnchorSnapshot(record.anchorSnapshot);
|
|
234
|
+
const elementId = normalizeDiagramElementId(record.elementId);
|
|
235
|
+
if (!anchorPoint || !anchorSnapshot || !elementId) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
anchorKind,
|
|
241
|
+
anchorPoint,
|
|
242
|
+
anchorQuote: normalizeCommentQuote(record.anchorQuote || anchorSnapshot.text),
|
|
243
|
+
anchorSnapshot,
|
|
244
|
+
elementId,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
199
248
|
const anchorStart = asObject(record.anchorStart);
|
|
200
249
|
const anchorEnd = asObject(record.anchorEnd);
|
|
201
250
|
const anchorStartLine = asFiniteNumber(record.anchorStartLine);
|
|
@@ -235,12 +284,18 @@ export function createCommentThreadSharedType(record = {}) {
|
|
|
235
284
|
messages.push(normalizedMessages.length > 0 ? normalizedMessages : [initialMessage]);
|
|
236
285
|
|
|
237
286
|
const thread = new Y.Map();
|
|
238
|
-
thread.set('anchorEnd', anchor.anchorEnd);
|
|
239
|
-
thread.set('anchorEndLine', anchor.anchorEndLine);
|
|
240
287
|
thread.set('anchorKind', anchor.anchorKind);
|
|
241
288
|
thread.set('anchorQuote', anchor.anchorQuote);
|
|
242
|
-
|
|
243
|
-
|
|
289
|
+
if (anchor.anchorKind === 'diagram-element') {
|
|
290
|
+
thread.set('anchorPoint', anchor.anchorPoint);
|
|
291
|
+
thread.set('anchorSnapshot', anchor.anchorSnapshot);
|
|
292
|
+
thread.set('elementId', anchor.elementId);
|
|
293
|
+
} else {
|
|
294
|
+
thread.set('anchorEnd', anchor.anchorEnd);
|
|
295
|
+
thread.set('anchorEndLine', anchor.anchorEndLine);
|
|
296
|
+
thread.set('anchorStart', anchor.anchorStart);
|
|
297
|
+
thread.set('anchorStartLine', anchor.anchorStartLine);
|
|
298
|
+
}
|
|
244
299
|
thread.set('createdAt', asFiniteNumber(record.createdAt) ?? Date.now());
|
|
245
300
|
thread.set('createdByColor', asString(record.createdByColor));
|
|
246
301
|
thread.set('createdByName', asString(record.createdByName) || initialMessage.userName);
|
|
@@ -266,6 +321,9 @@ export function serializeCommentThread(thread) {
|
|
|
266
321
|
anchorQuote: readThreadValue(thread, 'anchorQuote'),
|
|
267
322
|
anchorStart: readThreadValue(thread, 'anchorStart'),
|
|
268
323
|
anchorStartLine: readThreadValue(thread, 'anchorStartLine'),
|
|
324
|
+
anchorPoint: readThreadValue(thread, 'anchorPoint'),
|
|
325
|
+
anchorSnapshot: readThreadValue(thread, 'anchorSnapshot'),
|
|
326
|
+
elementId: readThreadValue(thread, 'elementId'),
|
|
269
327
|
});
|
|
270
328
|
const messages = serializeMessages(readThreadValue(thread, 'messages'));
|
|
271
329
|
|
|
@@ -98,13 +98,15 @@ function compareElementVersions(left, right) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
function compareElementIndex(left, right) {
|
|
101
|
-
const leftIndex = left?.index ?? '';
|
|
102
|
-
const rightIndex = right?.index ?? '';
|
|
101
|
+
const leftIndex = String(left?.index ?? '');
|
|
102
|
+
const rightIndex = String(right?.index ?? '');
|
|
103
103
|
if (leftIndex !== rightIndex) {
|
|
104
|
-
return
|
|
104
|
+
return leftIndex < rightIndex ? -1 : 1;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
const leftId = String(left?.id || '');
|
|
108
|
+
const rightId = String(right?.id || '');
|
|
109
|
+
return leftId < rightId ? -1 : leftId > rightId ? 1 : 0;
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
function getNestedMapValue(map, key, { create = false } = {}) {
|
package/src/domain/file-kind.js
CHANGED
|
@@ -4,20 +4,25 @@ const EXCALIDRAW_FILE_EXTENSION = '.excalidraw';
|
|
|
4
4
|
const DRAWIO_FILE_EXTENSION = '.drawio';
|
|
5
5
|
const MERMAID_FILE_EXTENSIONS = Object.freeze(['.mmd', '.mermaid']);
|
|
6
6
|
const PLANTUML_FILE_EXTENSIONS = Object.freeze(['.puml', '.plantuml']);
|
|
7
|
+
const STRUCTURIZR_FILE_EXTENSION = '.dsl';
|
|
8
|
+
const STRUCTURIZR_FILE_EXTENSIONS = Object.freeze([STRUCTURIZR_FILE_EXTENSION]);
|
|
9
|
+
const PDF_FILE_EXTENSION = '.pdf';
|
|
7
10
|
const IMAGE_ATTACHMENT_EXTENSIONS = Object.freeze(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg']);
|
|
8
11
|
const DIAGRAM_FILE_EXTENSIONS = Object.freeze([
|
|
9
12
|
EXCALIDRAW_FILE_EXTENSION,
|
|
10
13
|
DRAWIO_FILE_EXTENSION,
|
|
11
14
|
...MERMAID_FILE_EXTENSIONS,
|
|
12
15
|
...PLANTUML_FILE_EXTENSIONS,
|
|
16
|
+
...STRUCTURIZR_FILE_EXTENSIONS,
|
|
13
17
|
]);
|
|
14
18
|
const VAULT_FILE_EXTENSIONS = Object.freeze([
|
|
15
19
|
...MARKDOWN_FILE_EXTENSIONS,
|
|
16
20
|
BASE_FILE_EXTENSION,
|
|
17
21
|
...DIAGRAM_FILE_EXTENSIONS,
|
|
22
|
+
PDF_FILE_EXTENSION,
|
|
18
23
|
...IMAGE_ATTACHMENT_EXTENSIONS,
|
|
19
24
|
]);
|
|
20
|
-
const STRIP_VAULT_EXTENSION_PATTERN = /\.(?:md|markdown|mdx|base|excalidraw|drawio|mmd|mermaid|puml|plantuml|png|jpe?g|webp|gif|svg)$/i;
|
|
25
|
+
const STRIP_VAULT_EXTENSION_PATTERN = /\.(?:md|markdown|mdx|base|excalidraw|drawio|mmd|mermaid|puml|plantuml|dsl|pdf|png|jpe?g|webp|gif|svg)$/i;
|
|
21
26
|
|
|
22
27
|
function normalizeFilePath(filePath) {
|
|
23
28
|
return String(filePath ?? '').trim().toLowerCase();
|
|
@@ -36,7 +41,10 @@ export {
|
|
|
36
41
|
IMAGE_ATTACHMENT_EXTENSIONS,
|
|
37
42
|
MARKDOWN_FILE_EXTENSIONS,
|
|
38
43
|
MERMAID_FILE_EXTENSIONS,
|
|
44
|
+
PDF_FILE_EXTENSION,
|
|
39
45
|
PLANTUML_FILE_EXTENSIONS,
|
|
46
|
+
STRUCTURIZR_FILE_EXTENSION,
|
|
47
|
+
STRUCTURIZR_FILE_EXTENSIONS,
|
|
40
48
|
VAULT_FILE_EXTENSIONS,
|
|
41
49
|
};
|
|
42
50
|
|
|
@@ -65,6 +73,14 @@ export function getVaultFileKind(filePath) {
|
|
|
65
73
|
return 'plantuml';
|
|
66
74
|
}
|
|
67
75
|
|
|
76
|
+
if (hasFileExtension(filePath, STRUCTURIZR_FILE_EXTENSIONS)) {
|
|
77
|
+
return 'structurizr';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (hasFileExtension(filePath, [PDF_FILE_EXTENSION])) {
|
|
81
|
+
return 'pdf';
|
|
82
|
+
}
|
|
83
|
+
|
|
68
84
|
if (hasFileExtension(filePath, IMAGE_ATTACHMENT_EXTENSIONS)) {
|
|
69
85
|
return 'image';
|
|
70
86
|
}
|
|
@@ -118,13 +134,25 @@ export function isPlantUmlFilePath(filePath) {
|
|
|
118
134
|
return getVaultFileKind(filePath) === 'plantuml';
|
|
119
135
|
}
|
|
120
136
|
|
|
137
|
+
export function isStructurizrFilePath(filePath) {
|
|
138
|
+
return getVaultFileKind(filePath) === 'structurizr';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function isPdfFilePath(filePath) {
|
|
142
|
+
return getVaultFileKind(filePath) === 'pdf';
|
|
143
|
+
}
|
|
144
|
+
|
|
121
145
|
export function isImageAttachmentFilePath(filePath) {
|
|
122
146
|
return getVaultFileKind(filePath) === 'image';
|
|
123
147
|
}
|
|
124
148
|
|
|
125
149
|
export function isDiagramFilePath(filePath) {
|
|
126
150
|
const kind = getVaultFileKind(filePath);
|
|
127
|
-
return kind === 'excalidraw'
|
|
151
|
+
return kind === 'excalidraw'
|
|
152
|
+
|| kind === 'drawio'
|
|
153
|
+
|| kind === 'mermaid'
|
|
154
|
+
|| kind === 'plantuml'
|
|
155
|
+
|| kind === 'structurizr';
|
|
128
156
|
}
|
|
129
157
|
|
|
130
158
|
export function isVaultFilePath(filePath) {
|
|
@@ -133,7 +161,11 @@ export function isVaultFilePath(filePath) {
|
|
|
133
161
|
|
|
134
162
|
export function supportsCommentsForFilePath(filePath) {
|
|
135
163
|
const kind = getVaultFileKind(filePath);
|
|
136
|
-
return kind === 'markdown'
|
|
164
|
+
return kind === 'markdown'
|
|
165
|
+
|| kind === 'mermaid'
|
|
166
|
+
|| kind === 'plantuml'
|
|
167
|
+
|| kind === 'structurizr'
|
|
168
|
+
|| kind === 'excalidraw';
|
|
137
169
|
}
|
|
138
170
|
|
|
139
171
|
export function supportsBacklinksForFilePath(filePath) {
|
package/src/server/config/env.js
CHANGED
|
@@ -13,11 +13,28 @@ import {
|
|
|
13
13
|
import { loadBuildInfo } from './build-info.js';
|
|
14
14
|
import { isPerfLoggingEnabled } from './perf-logging.js';
|
|
15
15
|
|
|
16
|
+
const DEFAULT_SEARCH_MAX_BUFFER_BYTES = 2 * 1024 * 1024;
|
|
17
|
+
const DEFAULT_SEARCH_MAX_FILE_SIZE = '1M';
|
|
18
|
+
const DEFAULT_PDF_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
19
|
+
|
|
16
20
|
function parsePositiveInt(rawValue, fallbackValue) {
|
|
17
21
|
const parsed = Number.parseInt(rawValue ?? '', 10);
|
|
18
22
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallbackValue;
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
function normalizeSearchMaxFileSize(rawValue, fallbackValue = DEFAULT_SEARCH_MAX_FILE_SIZE) {
|
|
26
|
+
const normalized = String(rawValue ?? '').trim();
|
|
27
|
+
if (!normalized) {
|
|
28
|
+
return fallbackValue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!/^[1-9]\d*(?:[KMG])?$/iu.test(normalized)) {
|
|
32
|
+
throw new Error('COLLABMD_SEARCH_MAX_FILE_SIZE must be a positive byte count with an optional K, M, or G suffix.');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return normalized.toUpperCase();
|
|
36
|
+
}
|
|
37
|
+
|
|
21
38
|
function parseOptionalPositiveInt(rawValue, {
|
|
22
39
|
fallbackValue = null,
|
|
23
40
|
variableName = 'value',
|
|
@@ -56,6 +73,30 @@ function resolveDrawioBaseUrl(value) {
|
|
|
56
73
|
return normalizeOptionalString(value) || 'https://embed.diagrams.net';
|
|
57
74
|
}
|
|
58
75
|
|
|
76
|
+
function resolveStructurizrServerUrl(value) {
|
|
77
|
+
const normalized = normalizeOptionalString(value);
|
|
78
|
+
if (!normalized) {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let parsedUrl;
|
|
83
|
+
try {
|
|
84
|
+
parsedUrl = new URL(normalized);
|
|
85
|
+
} catch {
|
|
86
|
+
throw new Error('STRUCTURIZR_SERVER_URL must be an absolute HTTP or HTTPS URL.');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
|
|
90
|
+
throw new Error('STRUCTURIZR_SERVER_URL must use HTTP or HTTPS.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (parsedUrl.search || parsedUrl.hash) {
|
|
94
|
+
throw new Error('STRUCTURIZR_SERVER_URL must not include a query string or hash.');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return normalized.replace(/\/+$/u, '');
|
|
98
|
+
}
|
|
99
|
+
|
|
59
100
|
function normalizeAppBasePath(basePath) {
|
|
60
101
|
const normalized = normalizeOptionalString(basePath);
|
|
61
102
|
if (!normalized || normalized === '/') {
|
|
@@ -278,6 +319,31 @@ function loadOidcConfig(overrides = {}, { basePath = '' } = {}) {
|
|
|
278
319
|
};
|
|
279
320
|
}
|
|
280
321
|
|
|
322
|
+
function loadStructurizrConfig(overrides = {}, { vaultDir } = {}) {
|
|
323
|
+
const serverUrl = resolveStructurizrServerUrl(
|
|
324
|
+
overrides.serverUrl
|
|
325
|
+
?? process.env.COLLABMD_STRUCTURIZR_SERVER_URL
|
|
326
|
+
?? process.env.STRUCTURIZR_SERVER_URL,
|
|
327
|
+
);
|
|
328
|
+
const mirrorDir = resolveOptionalPath(
|
|
329
|
+
overrides.mirrorDir
|
|
330
|
+
?? process.env.COLLABMD_STRUCTURIZR_MIRROR_DIR
|
|
331
|
+
?? process.env.STRUCTURIZR_MIRROR_DIR,
|
|
332
|
+
) || resolve(vaultDir, '.collabmd/structurizr');
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
enabled: Boolean(serverUrl),
|
|
336
|
+
mirrorDir,
|
|
337
|
+
serverUrl,
|
|
338
|
+
trustedExecutableDsl: overrides.trustedExecutableDsl
|
|
339
|
+
?? parseBooleanFlag(
|
|
340
|
+
process.env.COLLABMD_STRUCTURIZR_TRUSTED_EXECUTABLE_DSL
|
|
341
|
+
?? process.env.STRUCTURIZR_TRUSTED_EXECUTABLE_DSL,
|
|
342
|
+
false,
|
|
343
|
+
),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
281
347
|
function loadHostedConfig(overrides = {}, { authStrategy, vaultDir } = {}) {
|
|
282
348
|
const enabled = overrides.enabled ?? parseBooleanFlag(process.env.COLLABMD_HOSTED_ENABLED, false);
|
|
283
349
|
const metadataDbPath = resolveOptionalPath(
|
|
@@ -361,6 +427,7 @@ export function loadConfig(overrides = {}) {
|
|
|
361
427
|
: null;
|
|
362
428
|
const git = loadGitConfig(overrides.git);
|
|
363
429
|
const hosted = loadHostedConfig(overrides.hosted, { authStrategy, vaultDir });
|
|
430
|
+
const structurizr = loadStructurizrConfig(overrides.structurizr, { vaultDir });
|
|
364
431
|
const build = loadBuildInfo({
|
|
365
432
|
explicitBuildId: process.env.COLLABMD_BUILD_ID,
|
|
366
433
|
projectRoot,
|
|
@@ -396,6 +463,18 @@ export function loadConfig(overrides = {}) {
|
|
|
396
463
|
maxBaseQueryRows: parsePositiveInt(process.env.COLLABMD_MAX_BASE_QUERY_ROWS, 5_000),
|
|
397
464
|
maxDownloadFileBytes: parsePositiveInt(process.env.COLLABMD_MAX_DOWNLOAD_FILE_BYTES, 268_435_456),
|
|
398
465
|
maxInitialSyncBytes: parsePositiveInt(process.env.COLLABMD_MAX_INITIAL_SYNC_BYTES, 16_777_216),
|
|
466
|
+
maxPdfUploadBytes: parsePositiveInt(
|
|
467
|
+
overrides.maxPdfUploadBytes ?? process.env.COLLABMD_MAX_PDF_UPLOAD_BYTES,
|
|
468
|
+
DEFAULT_PDF_UPLOAD_BYTES,
|
|
469
|
+
),
|
|
470
|
+
searchMaxBufferBytes: parsePositiveInt(
|
|
471
|
+
overrides.searchMaxBufferBytes ?? process.env.COLLABMD_SEARCH_MAX_BUFFER_BYTES,
|
|
472
|
+
DEFAULT_SEARCH_MAX_BUFFER_BYTES,
|
|
473
|
+
),
|
|
474
|
+
searchMaxFileSize: normalizeSearchMaxFileSize(
|
|
475
|
+
overrides.searchMaxFileSize ?? process.env.COLLABMD_SEARCH_MAX_FILE_SIZE,
|
|
476
|
+
),
|
|
477
|
+
structurizr,
|
|
399
478
|
perfLoggingEnabled: overrides.perfLoggingEnabled ?? isPerfLoggingEnabled(process.env.COLLABMD_PERF_LOGGING),
|
|
400
479
|
port: parsePositiveInt(process.env.PORT, 1234),
|
|
401
480
|
nodeEnv,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServer } from 'http';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
2
3
|
|
|
3
4
|
import { loadConfig } from './config/env.js';
|
|
4
5
|
import { createAuthService } from './auth/create-auth-service.js';
|
|
@@ -13,6 +14,7 @@ import { GitHubAppClient } from './infrastructure/github/github-app-client.js';
|
|
|
13
14
|
import { GitHubSetupFlow } from './infrastructure/github/github-setup-flow.js';
|
|
14
15
|
import { HostedWorkspaceService } from './domain/hosted-workspace.js';
|
|
15
16
|
import { PlantUmlRenderer } from './infrastructure/plantuml/plantuml-renderer.js';
|
|
17
|
+
import { StructurizrWorkspaceService } from './infrastructure/structurizr/structurizr-workspace-service.js';
|
|
16
18
|
import { RoomRegistry } from './domain/collaboration/room-registry.js';
|
|
17
19
|
import { RipgrepSearchService } from './domain/ripgrep-search-service.js';
|
|
18
20
|
import { createRequestHandler } from './infrastructure/http/create-request-handler.js';
|
|
@@ -83,12 +85,20 @@ export function createAppServer(config = loadConfig()) {
|
|
|
83
85
|
const plantUmlRenderer = new PlantUmlRenderer({
|
|
84
86
|
serverUrl: config.plantumlServerUrl,
|
|
85
87
|
});
|
|
88
|
+
const structurizrWorkspaceService = new StructurizrWorkspaceService({
|
|
89
|
+
mirrorDir: config.structurizr?.mirrorDir || resolve(config.vaultDir, '.collabmd/structurizr'),
|
|
90
|
+
serverUrl: config.structurizr?.serverUrl || '',
|
|
91
|
+
trustedExecutableDsl: config.structurizr?.trustedExecutableDsl,
|
|
92
|
+
vaultDir: config.vaultDir,
|
|
93
|
+
});
|
|
86
94
|
const gitService = new GitService({
|
|
87
95
|
commandEnv: config.git?.commandEnv,
|
|
88
96
|
enabled: config.gitEnabled,
|
|
89
97
|
vaultDir: config.vaultDir,
|
|
90
98
|
});
|
|
91
99
|
const searchService = new RipgrepSearchService({
|
|
100
|
+
maxBufferBytes: config.searchMaxBufferBytes,
|
|
101
|
+
maxFileSize: config.searchMaxFileSize,
|
|
92
102
|
perfLoggingEnabled: config.perfLoggingEnabled,
|
|
93
103
|
vaultDir: config.vaultDir,
|
|
94
104
|
});
|
|
@@ -153,6 +163,7 @@ export function createAppServer(config = loadConfig()) {
|
|
|
153
163
|
fileSystemSyncService,
|
|
154
164
|
hostedWorkspaceService,
|
|
155
165
|
githubSetupFlow,
|
|
166
|
+
structurizrWorkspaceService,
|
|
156
167
|
);
|
|
157
168
|
const httpServer = createServer((req, res) => {
|
|
158
169
|
requestHandler(req, res).catch((error) => {
|
|
@@ -315,6 +326,7 @@ export function createAppServer(config = loadConfig()) {
|
|
|
315
326
|
gitService,
|
|
316
327
|
hostedWorkspaceService,
|
|
317
328
|
searchService,
|
|
329
|
+
structurizrWorkspaceService,
|
|
318
330
|
setTestHydrateDelayMs(delayMs = 0) {
|
|
319
331
|
testControls.wsRoomHydrateDelayMs = Math.max(0, Number(delayMs) || 0);
|
|
320
332
|
},
|
|
@@ -2,7 +2,7 @@ import { dump as dumpYaml, loadAll as loadAllYaml } from 'js-yaml';
|
|
|
2
2
|
|
|
3
3
|
const SUPPORTED_VIEW_TYPES = new Set(['cards', 'list', 'table']);
|
|
4
4
|
|
|
5
|
-
function isPlainObject(value) {
|
|
5
|
+
export function isPlainObject(value) {
|
|
6
6
|
return Object.prototype.toString.call(value) === '[object Object]';
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { isImageAttachmentFilePath } from '../../../domain/file-kind.js';
|
|
2
2
|
import { resolveVaultRelativePath } from '../../../domain/vault-paths.js';
|
|
3
|
-
|
|
4
|
-
function isPlainObject(value) {
|
|
5
|
-
return Object.prototype.toString.call(value) === '[object Object]';
|
|
6
|
-
}
|
|
3
|
+
import { isPlainObject } from './base-definition.js';
|
|
7
4
|
|
|
8
5
|
export function createLinkValue(target, {
|
|
9
6
|
display = '',
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
createReferenceTargetAliasMap,
|
|
13
13
|
} from '../markdown-reference-extractor.js';
|
|
14
14
|
import { mapWithConcurrency } from '../../shared/async-utils.js';
|
|
15
|
+
import { isPlainObject } from './base-definition.js';
|
|
15
16
|
import { createLinkValue, dedupeLinkValues } from './base-expression-runtime.js';
|
|
16
17
|
|
|
17
18
|
const INLINE_TAG_RE = /(^|[\s(])#([A-Za-z0-9/_-]+)/g;
|
|
@@ -39,10 +40,6 @@ function createRowCacheKey(filePath, workspaceState, fileSetSignature) {
|
|
|
39
40
|
].join('\0');
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
function isPlainObject(value) {
|
|
43
|
-
return Object.prototype.toString.call(value) === '[object Object]';
|
|
44
|
-
}
|
|
45
|
-
|
|
46
43
|
function normalizeTags(value) {
|
|
47
44
|
if (Array.isArray(value)) {
|
|
48
45
|
return value
|
|
@@ -2,15 +2,12 @@ import { dump as dumpYaml } from 'js-yaml';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
findView,
|
|
5
|
+
isPlainObject,
|
|
5
6
|
normalizeBaseDefinition,
|
|
6
7
|
normalizeRawDefinitionForWrite,
|
|
7
8
|
parseBaseSource,
|
|
8
9
|
} from './base-definition.js';
|
|
9
10
|
|
|
10
|
-
function isPlainObject(value) {
|
|
11
|
-
return Object.prototype.toString.call(value) === '[object Object]';
|
|
12
|
-
}
|
|
13
|
-
|
|
14
11
|
function cloneRawSource(source = '') {
|
|
15
12
|
const parsed = parseBaseSource(source);
|
|
16
13
|
return isPlainObject(parsed) ? parsed : {};
|
|
@@ -6,9 +6,9 @@ import * as decoding from 'lib0/decoding';
|
|
|
6
6
|
|
|
7
7
|
import { MSG_AWARENESS, MSG_SYNC } from './protocol.js';
|
|
8
8
|
import { CollaborationDocumentStore } from './collaboration-document-store.js';
|
|
9
|
-
import { RoomPersistenceController } from './room-persistence-controller.js';
|
|
10
9
|
import { logPerfEvent } from '../../config/perf-logging.js';
|
|
11
10
|
import { populateCommentThreads, serializeCommentThreads } from '../../../domain/comment-threads.js';
|
|
11
|
+
import { getVaultFileKind } from '../../../domain/file-kind.js';
|
|
12
12
|
import {
|
|
13
13
|
EXCALIDRAW_APP_STATE_KEY,
|
|
14
14
|
EXCALIDRAW_ELEMENTS_KEY,
|
|
@@ -123,7 +123,7 @@ function readAwarenessEntries(update) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
function isExcalidrawRoom(name) {
|
|
126
|
-
return
|
|
126
|
+
return getVaultFileKind(name) === 'excalidraw';
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
function isWorkspaceRoom(name) {
|
|
@@ -214,15 +214,10 @@ export class CollaborationRoom {
|
|
|
214
214
|
lastHydrate: null,
|
|
215
215
|
lastInitialSyncAt: 0,
|
|
216
216
|
};
|
|
217
|
-
this.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
this.doc.destroy();
|
|
222
|
-
this.onEmpty?.(this.name);
|
|
223
|
-
},
|
|
224
|
-
onPersist: () => this.persist(),
|
|
225
|
-
});
|
|
217
|
+
this.persistTimer = null;
|
|
218
|
+
this.destroyTimer = null;
|
|
219
|
+
this.finalizePromise = null;
|
|
220
|
+
this.shutdownGeneration = 0;
|
|
226
221
|
|
|
227
222
|
this.awareness.setLocalState(null);
|
|
228
223
|
this.registerDocListeners();
|
|
@@ -475,11 +470,12 @@ export class CollaborationRoom {
|
|
|
475
470
|
return;
|
|
476
471
|
}
|
|
477
472
|
|
|
478
|
-
this.
|
|
473
|
+
clearTimeout(this.persistTimer);
|
|
474
|
+
this.persistTimer = setTimeout(() => {
|
|
479
475
|
this.persist().catch((error) => {
|
|
480
476
|
console.error(`[room:${this.name}] Failed to persist document:`, error.message);
|
|
481
477
|
});
|
|
482
|
-
});
|
|
478
|
+
}, 500);
|
|
483
479
|
}
|
|
484
480
|
|
|
485
481
|
async persist() {
|
|
@@ -620,9 +616,17 @@ export class CollaborationRoom {
|
|
|
620
616
|
this.documentStore?.rename(nextName);
|
|
621
617
|
}
|
|
622
618
|
|
|
619
|
+
cancelPersistenceTimers() {
|
|
620
|
+
this.shutdownGeneration += 1;
|
|
621
|
+
clearTimeout(this.persistTimer);
|
|
622
|
+
clearTimeout(this.destroyTimer);
|
|
623
|
+
this.persistTimer = null;
|
|
624
|
+
this.destroyTimer = null;
|
|
625
|
+
}
|
|
626
|
+
|
|
623
627
|
markDeleted() {
|
|
624
628
|
this.deleted = true;
|
|
625
|
-
this.
|
|
629
|
+
this.cancelPersistenceTimers();
|
|
626
630
|
if (this.clients.size === 0) {
|
|
627
631
|
this.finalizeIfIdle();
|
|
628
632
|
}
|
|
@@ -669,7 +673,9 @@ export class CollaborationRoom {
|
|
|
669
673
|
}
|
|
670
674
|
|
|
671
675
|
async addClient(ws, { sendInitialSync: shouldSendInitialSync = true } = {}) {
|
|
672
|
-
this.
|
|
676
|
+
this.shutdownGeneration += 1;
|
|
677
|
+
clearTimeout(this.destroyTimer);
|
|
678
|
+
this.destroyTimer = null;
|
|
673
679
|
await this.hydrate();
|
|
674
680
|
|
|
675
681
|
this.ensureClientState(ws);
|
|
@@ -721,9 +727,9 @@ export class CollaborationRoom {
|
|
|
721
727
|
}
|
|
722
728
|
|
|
723
729
|
this.destroyed = true;
|
|
724
|
-
this.
|
|
730
|
+
this.cancelPersistenceTimers();
|
|
725
731
|
await Promise.allSettled([
|
|
726
|
-
this.
|
|
732
|
+
this.finalizePromise,
|
|
727
733
|
this.activePersistPromise,
|
|
728
734
|
]);
|
|
729
735
|
|
|
@@ -745,12 +751,40 @@ export class CollaborationRoom {
|
|
|
745
751
|
}
|
|
746
752
|
|
|
747
753
|
finalizeIfIdle() {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
754
|
+
if (this.clients.size > 0 || this.finalizePromise) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const generation = ++this.shutdownGeneration;
|
|
759
|
+
this.finalizePromise = (async () => {
|
|
760
|
+
clearTimeout(this.persistTimer);
|
|
761
|
+
this.persistTimer = null;
|
|
762
|
+
|
|
763
|
+
try {
|
|
764
|
+
await this.persist();
|
|
765
|
+
} catch (error) {
|
|
751
766
|
console.error(`[room:${this.name}] Failed to persist final room state:`, error.message);
|
|
752
|
-
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (this.clients.size > 0 || generation !== this.shutdownGeneration) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
this.destroyTimer = setTimeout(() => {
|
|
774
|
+
if (this.clients.size > 0 || generation !== this.shutdownGeneration) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
this.awareness.destroy();
|
|
779
|
+
this.doc.destroy();
|
|
780
|
+
this.onEmpty?.(this.name);
|
|
781
|
+
}, this.idleGraceMs);
|
|
782
|
+
this.destroyTimer.unref?.();
|
|
783
|
+
})().finally(() => {
|
|
784
|
+
this.finalizePromise = null;
|
|
753
785
|
});
|
|
786
|
+
|
|
787
|
+
return this.finalizePromise;
|
|
754
788
|
}
|
|
755
789
|
|
|
756
790
|
clearEphemeralExcalidrawHistory() {
|
|
@@ -39,10 +39,16 @@ function createThreadSummary(thread) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const id = asString(thread.id);
|
|
42
|
+
const anchorKind = asString(thread.anchorKind) || 'line';
|
|
43
|
+
const isDiagramElementAnchor = anchorKind === 'diagram-element';
|
|
42
44
|
const anchorStartLine = asFiniteNumber(thread.anchorStartLine);
|
|
43
45
|
const anchorEndLine = asFiniteNumber(thread.anchorEndLine);
|
|
44
46
|
const messages = asArray(thread.messages);
|
|
45
|
-
if (
|
|
47
|
+
if (
|
|
48
|
+
!id
|
|
49
|
+
|| messages.length === 0
|
|
50
|
+
|| (!isDiagramElementAnchor && (anchorStartLine === null || anchorEndLine === null))
|
|
51
|
+
) {
|
|
46
52
|
return null;
|
|
47
53
|
}
|
|
48
54
|
|
|
@@ -53,10 +59,14 @@ function createThreadSummary(thread) {
|
|
|
53
59
|
|
|
54
60
|
return {
|
|
55
61
|
anchor: {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
...(isDiagramElementAnchor
|
|
63
|
+
? {}
|
|
64
|
+
: {
|
|
65
|
+
endLine: Math.max(anchorEndLine, anchorStartLine),
|
|
66
|
+
startLine: Math.max(anchorStartLine, 1),
|
|
67
|
+
}),
|
|
68
|
+
kind: anchorKind,
|
|
69
|
+
quote: asString(thread.anchorQuote) || (isDiagramElementAnchor ? 'Diagram element' : ''),
|
|
60
70
|
},
|
|
61
71
|
createdAt: asFiniteNumber(thread.createdAt) ?? latestActivityAt,
|
|
62
72
|
createdByColor: asString(thread.createdByColor),
|
|
@@ -22,7 +22,9 @@ const TEXT_SEARCH_GLOBS = Object.freeze([
|
|
|
22
22
|
'*.mermaid',
|
|
23
23
|
'*.puml',
|
|
24
24
|
'*.plantuml',
|
|
25
|
+
'*.dsl',
|
|
25
26
|
'*.drawio',
|
|
27
|
+
'*.excalidraw',
|
|
26
28
|
]);
|
|
27
29
|
|
|
28
30
|
function normalizePositiveInt(value, fallback, { min = 1, max = Number.MAX_SAFE_INTEGER } = {}) {
|
|
@@ -278,6 +280,7 @@ export class RipgrepSearchService {
|
|
|
278
280
|
async search({
|
|
279
281
|
limit = DEFAULT_MAX_FILES,
|
|
280
282
|
query = '',
|
|
283
|
+
signal = null,
|
|
281
284
|
} = {}) {
|
|
282
285
|
const normalizedQuery = String(query ?? '').trim();
|
|
283
286
|
const maxFiles = normalizePositiveInt(limit, this.maxFiles, {
|
|
@@ -310,6 +313,7 @@ export class RipgrepSearchService {
|
|
|
310
313
|
cwd: this.vaultDir,
|
|
311
314
|
encoding: 'utf8',
|
|
312
315
|
maxBuffer: this.maxBufferBytes,
|
|
316
|
+
...(signal ? { signal } : {}),
|
|
313
317
|
timeout: this.timeoutMs,
|
|
314
318
|
});
|
|
315
319
|
const parsed = parseRipgrepJson(result.stdout, {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { countPatchLines, parseNumstatOutput, parseUnifiedDiff } from './parsers.js';
|
|
2
2
|
import { createEmptyStats } from './responses.js';
|
|
3
|
+
import { getVaultFileKind } from '../../../domain/file-kind.js';
|
|
3
4
|
|
|
4
5
|
function createSectionMap(sections = []) {
|
|
5
6
|
return new Map(sections.map((section) => [section.key, section]));
|
|
@@ -33,6 +34,7 @@ function mergeScopedFile(existingFile, nextFile, scope) {
|
|
|
33
34
|
return {
|
|
34
35
|
...nextFile,
|
|
35
36
|
...nextFlags,
|
|
37
|
+
fileKind: nextFile.fileKind || getVaultFileKind(nextFile.path),
|
|
36
38
|
scope,
|
|
37
39
|
stats: createEmptyStats(),
|
|
38
40
|
};
|
|
@@ -44,6 +46,7 @@ function mergeScopedFile(existingFile, nextFile, scope) {
|
|
|
44
46
|
return {
|
|
45
47
|
...existingFile,
|
|
46
48
|
...nextFile,
|
|
49
|
+
fileKind: existingFile.fileKind || getVaultFileKind(nextFile.path),
|
|
47
50
|
hasStagedChanges: existingFile.hasStagedChanges || nextFlags.hasStagedChanges,
|
|
48
51
|
hasUntrackedChanges: existingFile.hasUntrackedChanges || nextFlags.hasUntrackedChanges,
|
|
49
52
|
hasWorkingTreeChanges: existingFile.hasWorkingTreeChanges || nextFlags.hasWorkingTreeChanges,
|
|
@@ -54,6 +57,7 @@ function mergeScopedFile(existingFile, nextFile, scope) {
|
|
|
54
57
|
|
|
55
58
|
return {
|
|
56
59
|
...existingFile,
|
|
60
|
+
fileKind: existingFile.fileKind || getVaultFileKind(nextFile.path),
|
|
57
61
|
hasStagedChanges: existingFile.hasStagedChanges || nextFlags.hasStagedChanges,
|
|
58
62
|
hasUntrackedChanges: existingFile.hasUntrackedChanges || nextFlags.hasUntrackedChanges,
|
|
59
63
|
hasWorkingTreeChanges: existingFile.hasWorkingTreeChanges || nextFlags.hasWorkingTreeChanges,
|
|
@@ -372,6 +376,7 @@ export class GitDiffService {
|
|
|
372
376
|
return {
|
|
373
377
|
...file,
|
|
374
378
|
...detail,
|
|
379
|
+
fileKind: file.fileKind || detail.fileKind || getVaultFileKind(file.path),
|
|
375
380
|
canLoadFullPatch: false,
|
|
376
381
|
patchLineCount,
|
|
377
382
|
tooLarge: false,
|