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
|
@@ -3,7 +3,7 @@ import { readdir } from 'node:fs/promises';
|
|
|
3
3
|
import { basename } from 'node:path';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
|
|
6
|
-
import { isImageAttachmentFilePath } from '../../../domain/file-kind.js';
|
|
6
|
+
import { getVaultFileKind, isImageAttachmentFilePath } from '../../../domain/file-kind.js';
|
|
7
7
|
import { isIgnoredVaultEntry } from '../persistence/path-utils.js';
|
|
8
8
|
import { parseJsonBody } from './request-body.js';
|
|
9
9
|
import {
|
|
@@ -19,6 +19,36 @@ const SVG_ATTACHMENT_CSP = "default-src 'none'; img-src 'self' data: blob:; styl
|
|
|
19
19
|
const DEFAULT_MAX_ARCHIVE_ENTRIES = 10_000;
|
|
20
20
|
const DEFAULT_MAX_DOWNLOAD_FILE_BYTES = 268_435_456;
|
|
21
21
|
|
|
22
|
+
function createSearchCancellation(req, res) {
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
|
|
25
|
+
const abort = () => {
|
|
26
|
+
if (controller.signal.aborted || res.writableEnded || res.writableFinished) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
controller.abort();
|
|
31
|
+
};
|
|
32
|
+
const handleRequestClose = () => {
|
|
33
|
+
if (!req.complete) {
|
|
34
|
+
abort();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
req.once('aborted', abort);
|
|
39
|
+
req.once('close', handleRequestClose);
|
|
40
|
+
res.once('close', abort);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
cleanup() {
|
|
44
|
+
req.off('aborted', abort);
|
|
45
|
+
req.off('close', handleRequestClose);
|
|
46
|
+
res.off('close', abort);
|
|
47
|
+
},
|
|
48
|
+
signal: controller.signal,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
22
52
|
function createAttachmentHeaders(attachment) {
|
|
23
53
|
const fileName = basename(String(attachment?.path ?? 'attachment'));
|
|
24
54
|
const headers = {
|
|
@@ -35,10 +65,10 @@ function createAttachmentHeaders(attachment) {
|
|
|
35
65
|
return headers;
|
|
36
66
|
}
|
|
37
67
|
|
|
38
|
-
function createDownloadHeaders(fileName, contentType) {
|
|
68
|
+
function createDownloadHeaders(fileName, contentType, { inline = false } = {}) {
|
|
39
69
|
return {
|
|
40
70
|
'Cache-Control': 'no-store',
|
|
41
|
-
'Content-Disposition':
|
|
71
|
+
'Content-Disposition': `${inline ? 'inline' : 'attachment'}; filename="${createSafeAsciiFilename(fileName)}"; filename*=UTF-8''${encodeContentDispositionFilename(fileName)}`,
|
|
42
72
|
'Content-Type': contentType,
|
|
43
73
|
'X-Content-Type-Options': 'nosniff',
|
|
44
74
|
};
|
|
@@ -304,6 +334,7 @@ async function handleFileDownload(req, res, requestUrl, { config, vaultFileStore
|
|
|
304
334
|
headers: createDownloadHeaders(
|
|
305
335
|
basename(String(download.path ?? 'download')),
|
|
306
336
|
download.mimeType || 'application/octet-stream',
|
|
337
|
+
{ inline: getVaultFileKind(download.path) === 'pdf' },
|
|
307
338
|
),
|
|
308
339
|
statusCode: 200,
|
|
309
340
|
stream: download.stream,
|
|
@@ -418,13 +449,28 @@ async function handleSearch(req, res, requestUrl, { searchService }) {
|
|
|
418
449
|
return;
|
|
419
450
|
}
|
|
420
451
|
|
|
452
|
+
const cancellation = createSearchCancellation(req, res);
|
|
453
|
+
|
|
421
454
|
try {
|
|
422
455
|
const result = await searchService.search({
|
|
423
456
|
limit: requestUrl.searchParams.get('limit') || '',
|
|
424
457
|
query: requestUrl.searchParams.get('q') || '',
|
|
458
|
+
signal: cancellation.signal,
|
|
425
459
|
});
|
|
460
|
+
if (cancellation.signal.aborted || res.destroyed || res.writableEnded) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
426
464
|
jsonResponse(req, res, 200, result);
|
|
427
465
|
} catch (error) {
|
|
466
|
+
if (
|
|
467
|
+
cancellation.signal.aborted
|
|
468
|
+
|| res.destroyed
|
|
469
|
+
|| res.writableEnded
|
|
470
|
+
) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
428
474
|
const statusCode = Number(error?.statusCode) || 500;
|
|
429
475
|
console.error('[api] Failed to search vault:', error.message);
|
|
430
476
|
jsonResponse(req, res, statusCode, {
|
|
@@ -435,6 +481,8 @@ async function handleSearch(req, res, requestUrl, { searchService }) {
|
|
|
435
481
|
backend: 'ripgrep',
|
|
436
482
|
},
|
|
437
483
|
});
|
|
484
|
+
} finally {
|
|
485
|
+
cancellation.cleanup();
|
|
438
486
|
}
|
|
439
487
|
}
|
|
440
488
|
|
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import { getRequestErrorStatusCode } from './http-errors.js';
|
|
2
2
|
import { jsonResponse } from './http-response.js';
|
|
3
3
|
|
|
4
|
+
export function stripBasePath(pathname, basePath) {
|
|
5
|
+
if (!basePath) {
|
|
6
|
+
return pathname;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (pathname === basePath) {
|
|
10
|
+
return '/';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (pathname.startsWith(`${basePath}/`)) {
|
|
14
|
+
return pathname.slice(basePath.length) || '/';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return pathname;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createRequestUrlWithPathname(requestUrl, pathname) {
|
|
21
|
+
const nextUrl = new URL(requestUrl.toString());
|
|
22
|
+
nextUrl.pathname = pathname || '/';
|
|
23
|
+
return nextUrl;
|
|
24
|
+
}
|
|
25
|
+
|
|
4
26
|
export function readRequestId(req) {
|
|
5
27
|
const value = String(req.headers['x-collabmd-request-id'] || '').trim();
|
|
6
28
|
return value ? value.slice(0, 120) : null;
|
|
@@ -3,7 +3,7 @@ import { isAbsolute, normalize, relative, resolve } from 'path';
|
|
|
3
3
|
import { isVaultFilePath } from '../../../domain/file-kind.js';
|
|
4
4
|
|
|
5
5
|
export const IGNORED_DIRECTORIES = new Set(['.git', '.obsidian', '.trash', 'node_modules', '.DS_Store']);
|
|
6
|
-
export const VAULT_FILE_PATH_REQUIREMENT = '.md, .base, .excalidraw, .drawio, .mmd, .mermaid, .puml, .plantuml, .png, .jpg, .jpeg, .webp, .gif, or .svg';
|
|
6
|
+
export const VAULT_FILE_PATH_REQUIREMENT = '.md, .markdown, .mdx, .base, .excalidraw, .drawio, .mmd, .mermaid, .puml, .plantuml, .dsl, .png, .jpg, .jpeg, .webp, .gif, or .svg';
|
|
7
7
|
export const INVALID_VAULT_FILE_PATH_ERROR = `Invalid file path — must end in ${VAULT_FILE_PATH_REQUIREMENT}`;
|
|
8
8
|
export const INVALID_DIRECTORY_PATH_ERROR = 'Invalid directory path';
|
|
9
9
|
|
|
@@ -31,6 +31,7 @@ const EDITABLE_VAULT_CONTENT_PATH_ERRORS = {
|
|
|
31
31
|
markdown: 'Invalid file path',
|
|
32
32
|
mermaid: 'Invalid file path — must end in .mmd or .mermaid',
|
|
33
33
|
plantuml: 'Invalid file path — must end in .puml or .plantuml',
|
|
34
|
+
structurizr: 'Invalid file path — must end in .dsl',
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
function getEditableVaultContentKind(filePath) {
|
|
@@ -67,7 +68,9 @@ const TEXT_FILE_MIME_TYPES = Object.freeze({
|
|
|
67
68
|
excalidraw: 'application/json; charset=utf-8',
|
|
68
69
|
markdown: 'text/markdown; charset=utf-8',
|
|
69
70
|
mermaid: 'text/plain; charset=utf-8',
|
|
71
|
+
pdf: 'application/pdf',
|
|
70
72
|
plantuml: 'text/plain; charset=utf-8',
|
|
73
|
+
structurizr: 'text/plain; charset=utf-8',
|
|
71
74
|
});
|
|
72
75
|
|
|
73
76
|
function createTransactionalPath(targetPath, label) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
html, body, #content {
|
|
2
|
+
height: 100% !important;
|
|
3
|
+
min-height: 0 !important;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
margin: 0 !important;
|
|
8
|
+
overflow: hidden !important;
|
|
9
|
+
background: transparent !important;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#diagramControls,
|
|
13
|
+
#diagramNavigationPanel,
|
|
14
|
+
#embeddedControls {
|
|
15
|
+
display: none !important;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#diagramControls + .row,
|
|
19
|
+
#diagramControls + .row > .col-10,
|
|
20
|
+
#diagramControls + .row #diagram,
|
|
21
|
+
#diagram-viewport {
|
|
22
|
+
height: 100% !important;
|
|
23
|
+
min-height: 0 !important;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#diagramControls + .row > .col-10 {
|
|
27
|
+
width: 100% !important;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#diagram {
|
|
31
|
+
background: transparent !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#diagram-viewport {
|
|
35
|
+
overflow: auto !important;
|
|
36
|
+
touch-action: pan-x pan-y !important;
|
|
37
|
+
overscroll-behavior: contain !important;
|
|
38
|
+
background: transparent !important;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#diagram-canvas {
|
|
42
|
+
margin: 0 auto !important;
|
|
43
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, readdir, realpath, rm, stat, utimes, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
isStructurizrFilePath,
|
|
7
|
+
isVaultFilePath,
|
|
8
|
+
} from '../../../domain/file-kind.js';
|
|
9
|
+
import { sanitizeVaultPath } from '../persistence/path-utils.js';
|
|
10
|
+
|
|
11
|
+
const MANIFEST_FILE_NAME = '.collabmd-manifest.json';
|
|
12
|
+
const WORKSPACE_DSL_FILE_NAME = 'workspace.dsl';
|
|
13
|
+
const WORKSPACE_JSON_FILE_NAME = 'workspace.json';
|
|
14
|
+
const SIDECAR_TIMEOUT_MS = 15_000;
|
|
15
|
+
|
|
16
|
+
function createServiceError(message, statusCode, code) {
|
|
17
|
+
const error = new Error(message);
|
|
18
|
+
error.statusCode = statusCode;
|
|
19
|
+
error.requestCode = code;
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeWorkspaceRootPath(pathValue) {
|
|
24
|
+
const normalized = String(pathValue ?? '').trim().replace(/\\/g, '/');
|
|
25
|
+
if (!normalized || normalized.startsWith('/') || normalized.split('/').some((segment) => !segment || segment === '.' || segment === '..')) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function validateStructurizrDsl(source, { trustedExecutableDsl = false } = {}) {
|
|
33
|
+
if (trustedExecutableDsl) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const text = String(source ?? '');
|
|
38
|
+
if (/^\s*!script\b/im.test(text) || /^\s*!plugin\b/im.test(text)) {
|
|
39
|
+
throw createServiceError(
|
|
40
|
+
'Executable Structurizr DSL is disabled for this workspace.',
|
|
41
|
+
422,
|
|
42
|
+
'STRUCTURIZR_EXECUTABLE_DSL_DISABLED',
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (/^\s*!include\s+<?(?:https?|file):/im.test(text)) {
|
|
47
|
+
throw createServiceError(
|
|
48
|
+
'Remote Structurizr DSL includes are disabled for this workspace.',
|
|
49
|
+
422,
|
|
50
|
+
'STRUCTURIZR_REMOTE_INCLUDE_DISABLED',
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isMirrorFileAllowed(relativePath) {
|
|
56
|
+
return basename(relativePath) === WORKSPACE_JSON_FILE_NAME || isVaultFilePath(relativePath);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function collectWorkspaceFiles(directoryPath, prefix = '') {
|
|
60
|
+
const files = new Map();
|
|
61
|
+
const entries = (await readdir(directoryPath, { withFileTypes: true }))
|
|
62
|
+
.sort((left, right) => left.name.localeCompare(right.name, undefined, { sensitivity: 'base' }));
|
|
63
|
+
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === '.git') {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
70
|
+
const absolutePath = join(directoryPath, entry.name);
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
const nested = await collectWorkspaceFiles(absolutePath, relativePath);
|
|
73
|
+
nested.forEach((content, pathValue) => files.set(pathValue, content));
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!entry.isFile() || !isMirrorFileAllowed(relativePath)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
files.set(relativePath, await readFile(absolutePath));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return files;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function collectMirrorPaths(directoryPath, prefix = '') {
|
|
88
|
+
let entries;
|
|
89
|
+
try {
|
|
90
|
+
entries = await readdir(directoryPath, { withFileTypes: true });
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error.code === 'ENOENT') {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const paths = [];
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
if (entry.name === '.structurizr') {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
105
|
+
const absolutePath = join(directoryPath, entry.name);
|
|
106
|
+
if (entry.isDirectory()) {
|
|
107
|
+
paths.push(...await collectMirrorPaths(absolutePath, relativePath));
|
|
108
|
+
} else if (entry.isFile()) {
|
|
109
|
+
paths.push(relativePath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return paths;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function readManifest(manifestPath) {
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
119
|
+
return Array.isArray(parsed?.paths) ? parsed.paths : [];
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error.code === 'ENOENT' || error instanceof SyntaxError) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function writeMirrorFiles(mirrorDir, files) {
|
|
129
|
+
await Promise.all(Array.from(files, async ([relativePath, content]) => {
|
|
130
|
+
const targetPath = join(mirrorDir, relativePath);
|
|
131
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
132
|
+
await writeFile(targetPath, content);
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function removeMirrorPaths(mirrorDir, paths) {
|
|
137
|
+
await Promise.all(paths.map((relativePath) => rm(join(mirrorDir, relativePath), {
|
|
138
|
+
force: true,
|
|
139
|
+
recursive: true,
|
|
140
|
+
})));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function snapshotMirror(mirrorDir) {
|
|
144
|
+
const paths = await collectMirrorPaths(mirrorDir);
|
|
145
|
+
const files = new Map();
|
|
146
|
+
await Promise.all(paths.map(async (relativePath) => {
|
|
147
|
+
files.set(relativePath, await readFile(join(mirrorDir, relativePath)));
|
|
148
|
+
}));
|
|
149
|
+
return files;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function restoreMirror(mirrorDir, snapshot) {
|
|
153
|
+
await removeMirrorPaths(mirrorDir, await collectMirrorPaths(mirrorDir));
|
|
154
|
+
await writeMirrorFiles(mirrorDir, snapshot);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function hashFiles(files) {
|
|
158
|
+
const hash = createHash('sha256');
|
|
159
|
+
Array.from(files.keys()).sort().forEach((pathValue) => {
|
|
160
|
+
hash.update(pathValue);
|
|
161
|
+
hash.update('\0');
|
|
162
|
+
hash.update(files.get(pathValue));
|
|
163
|
+
hash.update('\0');
|
|
164
|
+
});
|
|
165
|
+
return hash.digest('hex');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export class StructurizrWorkspaceService {
|
|
169
|
+
constructor({
|
|
170
|
+
mirrorDir,
|
|
171
|
+
serverUrl = '',
|
|
172
|
+
trustedExecutableDsl = false,
|
|
173
|
+
vaultDir,
|
|
174
|
+
}) {
|
|
175
|
+
this.mirrorDir = resolve(mirrorDir);
|
|
176
|
+
this.serverUrl = String(serverUrl ?? '').trim().replace(/\/+$/u, '');
|
|
177
|
+
this.trustedExecutableDsl = Boolean(trustedExecutableDsl);
|
|
178
|
+
this.vaultDir = resolve(vaultDir);
|
|
179
|
+
this.syncPromise = Promise.resolve();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
isEnabled() {
|
|
183
|
+
return Boolean(this.serverUrl);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
isProxyPath(pathname) {
|
|
187
|
+
return this.isEnabled() && (
|
|
188
|
+
pathname === '/workspace/1'
|
|
189
|
+
|| pathname.startsWith('/workspace/1/')
|
|
190
|
+
|| pathname.startsWith('/static/')
|
|
191
|
+
|| pathname === '/api/workspace/1'
|
|
192
|
+
|| pathname.startsWith('/api/workspace/1/')
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async sync({ content, rootPath }) {
|
|
197
|
+
if (!this.isEnabled()) {
|
|
198
|
+
throw createServiceError('Structurizr renderer is not configured.', 503, 'STRUCTURIZR_NOT_CONFIGURED');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const run = async () => this.syncWorkspace({ content, rootPath });
|
|
202
|
+
this.syncPromise = this.syncPromise.catch(() => {}).then(run);
|
|
203
|
+
return this.syncPromise;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async syncWorkspace({ content, rootPath }) {
|
|
207
|
+
const normalizedRootPath = normalizeWorkspaceRootPath(rootPath);
|
|
208
|
+
if (!isStructurizrFilePath(normalizedRootPath)) {
|
|
209
|
+
throw createServiceError('Structurizr previews require a workspace.dsl root file.', 400, 'STRUCTURIZR_ROOT_REQUIRED');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const rootAbsolutePath = sanitizeVaultPath(this.vaultDir, normalizedRootPath);
|
|
213
|
+
if (!rootAbsolutePath) {
|
|
214
|
+
throw createServiceError('Invalid Structurizr workspace path.', 400, 'STRUCTURIZR_ROOT_INVALID');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let rootInfo;
|
|
218
|
+
try {
|
|
219
|
+
rootInfo = await stat(rootAbsolutePath);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error.code === 'ENOENT') {
|
|
222
|
+
throw createServiceError('Structurizr workspace not found.', 404, 'STRUCTURIZR_ROOT_NOT_FOUND');
|
|
223
|
+
}
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
if (!rootInfo.isFile()) {
|
|
227
|
+
throw createServiceError('Structurizr workspace root is not a file.', 400, 'STRUCTURIZR_ROOT_INVALID');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const [vaultRealPath, rootRealPath] = await Promise.all([
|
|
231
|
+
realpath(this.vaultDir),
|
|
232
|
+
realpath(rootAbsolutePath),
|
|
233
|
+
]);
|
|
234
|
+
if (rootRealPath !== vaultRealPath && !rootRealPath.startsWith(`${vaultRealPath}/`)) {
|
|
235
|
+
throw createServiceError('Structurizr workspace must remain inside the vault.', 400, 'STRUCTURIZR_ROOT_INVALID');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const sourceDirectory = dirname(rootRealPath);
|
|
239
|
+
const files = await collectWorkspaceFiles(sourceDirectory);
|
|
240
|
+
const sourceRootFileName = basename(normalizedRootPath);
|
|
241
|
+
const rootSource = typeof content === 'string'
|
|
242
|
+
? content
|
|
243
|
+
: files.get(sourceRootFileName)?.toString('utf8');
|
|
244
|
+
if (typeof rootSource !== 'string') {
|
|
245
|
+
throw createServiceError('Structurizr workspace source is unavailable.', 400, 'STRUCTURIZR_ROOT_UNREADABLE');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
files.delete(sourceRootFileName);
|
|
249
|
+
files.set(WORKSPACE_DSL_FILE_NAME, Buffer.from(rootSource, 'utf8'));
|
|
250
|
+
for (const [pathValue, fileContent] of files) {
|
|
251
|
+
if (pathValue.toLowerCase().endsWith('.dsl')) {
|
|
252
|
+
validateStructurizrDsl(fileContent.toString('utf8'), {
|
|
253
|
+
trustedExecutableDsl: this.trustedExecutableDsl,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
await mkdir(this.mirrorDir, { recursive: true });
|
|
259
|
+
const snapshot = await snapshotMirror(this.mirrorDir);
|
|
260
|
+
const previousManifest = await readManifest(join(this.mirrorDir, MANIFEST_FILE_NAME));
|
|
261
|
+
const sourcePaths = Array.from(files.keys());
|
|
262
|
+
const stalePaths = previousManifest.filter((pathValue) => !files.has(pathValue));
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
await removeMirrorPaths(this.mirrorDir, stalePaths);
|
|
266
|
+
await writeMirrorFiles(this.mirrorDir, files);
|
|
267
|
+
// Local caches workspace.json; an old mtime makes it parse the DSL without a delete race.
|
|
268
|
+
const workspaceJsonPath = join(this.mirrorDir, WORKSPACE_JSON_FILE_NAME);
|
|
269
|
+
try {
|
|
270
|
+
await utimes(workspaceJsonPath, new Date(0), new Date(0));
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (error.code !== 'ENOENT') {
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
await this.validateMirror();
|
|
277
|
+
if (files.has(WORKSPACE_JSON_FILE_NAME)) {
|
|
278
|
+
await writeMirrorFiles(this.mirrorDir, new Map([
|
|
279
|
+
[WORKSPACE_JSON_FILE_NAME, files.get(WORKSPACE_JSON_FILE_NAME)],
|
|
280
|
+
]));
|
|
281
|
+
}
|
|
282
|
+
await writeFile(
|
|
283
|
+
join(this.mirrorDir, MANIFEST_FILE_NAME),
|
|
284
|
+
`${JSON.stringify({ paths: sourcePaths }, null, 2)}\n`,
|
|
285
|
+
'utf8',
|
|
286
|
+
);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
await restoreMirror(this.mirrorDir, snapshot);
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
ok: true,
|
|
294
|
+
rootPath: normalizedRootPath,
|
|
295
|
+
version: hashFiles(files),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async validateMirror() {
|
|
300
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
301
|
+
let response;
|
|
302
|
+
try {
|
|
303
|
+
response = await fetch(`${this.serverUrl}/api/workspace/1`, {
|
|
304
|
+
headers: { Accept: 'application/json' },
|
|
305
|
+
signal: AbortSignal.timeout(SIDECAR_TIMEOUT_MS),
|
|
306
|
+
});
|
|
307
|
+
} catch (error) {
|
|
308
|
+
throw createServiceError(
|
|
309
|
+
`Structurizr renderer is unavailable: ${error.message}`,
|
|
310
|
+
503,
|
|
311
|
+
'STRUCTURIZR_UNAVAILABLE',
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const payload = await response.json().catch(() => ({}));
|
|
316
|
+
if (response.ok && payload.success !== false) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const message = String(payload.message || payload.error || `Structurizr renderer returned HTTP ${response.status}`)
|
|
321
|
+
.replace(/^com\.structurizr\.dsl\.StructurizrDslParserException:\s*/u, '');
|
|
322
|
+
const isTransientStartupFailure = response.status >= 500
|
|
323
|
+
&& /(?:No workspace|FileNotFoundException|workspace\.json)/iu.test(message);
|
|
324
|
+
if (isTransientStartupFailure && attempt < 7) {
|
|
325
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 250));
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
throw createServiceError(
|
|
330
|
+
message,
|
|
331
|
+
response.status >= 400 && response.status < 500 ? 422 : 502,
|
|
332
|
+
response.status >= 400 && response.status < 500
|
|
333
|
+
? 'STRUCTURIZR_DSL_INVALID'
|
|
334
|
+
: 'STRUCTURIZR_VALIDATION_FAILED',
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async proxy(requestUrl, { method = 'GET', headers = {} } = {}) {
|
|
340
|
+
if (!this.isProxyPath(requestUrl.pathname)) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const upstreamUrl = `${this.serverUrl}${requestUrl.pathname}${requestUrl.search}`;
|
|
345
|
+
let response;
|
|
346
|
+
try {
|
|
347
|
+
const proxyHeaders = {
|
|
348
|
+
Accept: headers.accept || '*/*',
|
|
349
|
+
};
|
|
350
|
+
if (headers.range) {
|
|
351
|
+
proxyHeaders.Range = headers.range;
|
|
352
|
+
}
|
|
353
|
+
response = await fetch(upstreamUrl, {
|
|
354
|
+
headers: proxyHeaders,
|
|
355
|
+
method,
|
|
356
|
+
redirect: 'follow',
|
|
357
|
+
signal: AbortSignal.timeout(SIDECAR_TIMEOUT_MS),
|
|
358
|
+
});
|
|
359
|
+
} catch (error) {
|
|
360
|
+
throw createServiceError(
|
|
361
|
+
`Structurizr renderer is unavailable: ${error.message}`,
|
|
362
|
+
503,
|
|
363
|
+
'STRUCTURIZR_UNAVAILABLE',
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
body: Buffer.from(await response.arrayBuffer()),
|
|
369
|
+
contentType: response.headers.get('content-type') || 'application/octet-stream',
|
|
370
|
+
statusCode: response.status,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { WebSocketServer } from 'ws';
|
|
2
2
|
|
|
3
3
|
import { normalizeHostedEmail } from '../../domain/hosted-workspace-contract.js';
|
|
4
|
+
import {
|
|
5
|
+
createRequestUrlWithPathname,
|
|
6
|
+
stripBasePath,
|
|
7
|
+
} from '../http/http-request-helpers.js';
|
|
4
8
|
import { ClientSocketSession } from './client-socket-session.js';
|
|
5
9
|
|
|
6
10
|
function rejectUpgrade(socket, statusCode, statusMessage, {
|
|
@@ -25,28 +29,6 @@ function extractRoomName(pathname, wsBasePath) {
|
|
|
25
29
|
return decodeURIComponent(roomSegment || 'default');
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
function stripBasePath(pathname, basePath) {
|
|
29
|
-
if (!basePath) {
|
|
30
|
-
return pathname;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (pathname === basePath) {
|
|
34
|
-
return '/';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (pathname.startsWith(`${basePath}/`)) {
|
|
38
|
-
return pathname.slice(basePath.length) || '/';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return pathname;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function createRequestUrlWithPathname(requestUrl, pathname) {
|
|
45
|
-
const nextUrl = new URL(requestUrl.toString());
|
|
46
|
-
nextUrl.pathname = pathname || '/';
|
|
47
|
-
return nextUrl;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
32
|
export function attachCollaborationGateway({
|
|
51
33
|
authService,
|
|
52
34
|
basePath = '',
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as e}from"./rolldown-runtime-Dd_uD5pT.js";import{A as t,F as n,J as r,L as i,P as a,V as o,X as s,c,lt as l,q as u,st as d,t as f}from"./dist-C11hHqv_.js";import"./chunk-SRAX5OIU-KFQikY-4.js";import{n as p,t as m}from"./jsx-runtime-BpzPEenQ.js";import{n as h,r as g,s as _,t as v}from"./dist-GrygD2zE.js";var y=e(p(),1),b=m(),x=c.define({token(e){return e.match(/^%%.*$/)?`comment`:e.match(/^"(?:[^"\\]|\\.)*"/)?`string`:e.match(/^(flowchart|graph|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|mindmap|journey|gitGraph|timeline|quadrantChart|sankey|xychart)\b/i)||e.match(/^(TB|TD|BT|RL|LR)\b/)||e.match(/^(subgraph|end|participant|actor|loop|alt|else|opt|par|critical|break|rect|note|over|activate|deactivate|title|section|class|style|linkStyle|classDef|click)\b/i)?`keyword`:e.match(/^[-.=<>|ox]+>/)||e.match(/^<[-.=<>|ox]+/)||e.match(/^--+|\.\.+|==+/)?`operator`:e.match(/^[[\](){}|<>]/)?`bracket`:e.match(/^[A-Za-z_][A-Za-z0-9_]*/)?`variableName`:e.match(/^\d+(\.\d+)?/)?`number`:e.match(/^[,:;]/)?`punctuation`:(e.eatSpace()||e.next(),null)}});function S(){return x}var C=i.theme({"&":{backgroundColor:`#1e1e1e`,color:`#d4d4d4`},".cm-content":{caretColor:`#fff`},".cm-cursor":{borderLeftColor:`#fff`},".cm-gutters":{backgroundColor:`#1e1e1e`,color:`#858585`,border:`none`},".cm-activeLineGutter":{backgroundColor:`#2a2a2a`},".cm-activeLine":{backgroundColor:`#2a2a2a`},".cm-errorLine":{backgroundColor:`rgba(255, 0, 0, 0.15)`}},{dark:!0}),w=f.define([{tag:a.keyword,color:`#569cd6`},{tag:a.string,color:`#ce9178`},{tag:a.comment,color:`#6a9955`},{tag:a.number,color:`#b5cea8`},{tag:a.operator,color:`#d4d4d4`},{tag:a.punctuation,color:`#d4d4d4`},{tag:a.variableName,color:`#9cdcfe`},{tag:a.bracket,color:`#ffd700`}]),T=i.theme({"&":{backgroundColor:`#ffffff`,color:`#1e1e1e`},".cm-content":{caretColor:`#000`},".cm-cursor":{borderLeftColor:`#000`},".cm-gutters":{backgroundColor:`#fff`,color:`#999`,border:`none`},".cm-activeLineGutter":{backgroundColor:`#e8e8e8`},".cm-activeLine":{backgroundColor:`#e8e8e8`},".cm-errorLine":{backgroundColor:`rgba(255, 0, 0, 0.1)`}}),E=f.define([{tag:a.keyword,color:`#0000ff`},{tag:a.string,color:`#a31515`},{tag:a.comment,color:`#008000`},{tag:a.number,color:`#098658`},{tag:a.operator,color:`#1e1e1e`},{tag:a.punctuation,color:`#1e1e1e`},{tag:a.variableName,color:`#001080`},{tag:a.bracket,color:`#af00db`}]),D=n.line({class:`cm-errorLine`}),O=(e,t)=>{if(!e||e<1||e>t.lines)return i.decorations.of(n.none);let r=t.line(e);return i.decorations.of(n.set([D.range(r.from)]))},k=e=>e===`dark`?[C,t(w)]:[T,t(E)],A=({value:e,onChange:t,onKeyboardSubmit:n,placeholder:a,theme:c,errorLine:f})=>{let p=(0,y.useRef)(null),m=(0,y.useRef)(null),x=(0,y.useRef)(t),C=(0,y.useRef)(n),w=(0,y.useRef)(new d),T=(0,y.useRef)(new d);return x.current=t,C.current=n,(0,y.useEffect)(()=>{if(!p.current)return;let t=w.current,n=new i({state:l.create({doc:e,extensions:[u.of([{key:`Mod-Enter`,run:()=>(C.current?.(),!0)},{key:`Mod-Shift-z`,run:_,preventDefault:!0}]),i.updateListener.of(e=>{e.docChanged&&x.current(e.state.doc.toString())}),h(),u.of([...v,...g]),r(),i.lineWrapping,t.of(k(c)),T.current.of([]),S(),o({drawRangeCursor:!0}),...a?[s(a)]:[]]}),parent:p.current});return m.current=n,n.focus(),()=>{n.destroy(),m.current=null}},[]),(0,y.useEffect)(()=>{let e=m.current;e&&e.dispatch({effects:w.current.reconfigure(k(c))})},[c]),(0,y.useEffect)(()=>{let e=m.current;e&&e.dispatch({effects:T.current.reconfigure(O(f,e.state.doc))})},[f]),(0,y.useEffect)(()=>{let t=m.current;if(!t)return;let n=t.state.doc.toString();e!==n&&t.dispatch({changes:{from:0,to:n.length,insert:e}})},[e]),(0,b.jsx)(`div`,{ref:p,className:`ttd-dialog-input ttd-dialog-input--codemirror`})};export{A as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function e(){return typeof window>`u`?globalThis:window}function t(t,n){let r=e();return typeof r.requestIdleCallback==`function`?r.requestIdleCallback(t,{timeout:n}):r.setTimeout(()=>t({didTimeout:!1,timeRemaining:()=>0}),1)}function n(t){if(t===null)return;let n=e();if(typeof n.cancelIdleCallback==`function`){n.cancelIdleCallback(t);return}n.clearTimeout(t)}function r(e,t,n){if(!e||!t)return!1;let r=t.getBoundingClientRect(),i=e.getBoundingClientRect();return i.bottom>=r.top-n&&i.top<=r.bottom+n}function i(t,n,{removeDelayMs:r=3e4,revokeDelayMs:i=r}={}){let a=e(),o=URL.createObjectURL(t),s=document.createElement(`a`);s.href=o,s.download=n,s.style&&(s.style.display=`none`),document.body.appendChild(s),s.click(),r>0?a.setTimeout(()=>s.remove(),r):s.remove(),a.setTimeout(()=>URL.revokeObjectURL(o),i)}function a(e=``,t=`download`){let n=String(e).match(/filename\*=UTF-8''([^;]+)/iu);if(n)try{return decodeURIComponent(n[1])}catch{return t}return String(e).match(/filename="([^"]+)"/iu)?.[1]||t}export{t as a,a as i,i as n,r,n as t};
|