collabmd 0.1.44 → 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/{chunk-CZQZEIFA-DT3N9fo7.js → chunk-YGJVY4SA-CjSe7Bx7.js} +2 -2
- 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-FzY1cVw8.js +9 -0
- 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/{en-GADW4C6J-DQ6zyCEM.js → en-7NFCYGKX-BrgYZAEe.js} +1 -1
- 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-Bt7gMSoh.css +1 -0
- 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-A7NHawJY.js +39 -0
- 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/modulepreload-polyfill-P2Xu9kJm.js +1 -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-diagram-utils-txBCoW8d.js +1 -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/assets/{yjs-provider-reset-guard-Cox4nxwu.js → yjs-provider-reset-guard-DE0vINDP.js} +1 -1
- package/dist/client/drawio-editor.html +3 -2
- package/dist/client/excalidraw-editor.html +3 -2
- package/dist/client/export-document.html +4 -3
- package/dist/client/index.html +109 -27
- package/docker-compose.yml +42 -0
- package/package.json +26 -15
- package/scripts/local-structurizr-compose.mjs +101 -0
- package/src/client/app/drawio-editor-entry.js +0 -3
- package/src/client/app/drawio-editor.html +1 -0
- package/src/client/app/excalidraw-editor-entry.js +0 -3
- package/src/client/app/excalidraw-editor.html +1 -0
- package/src/client/app/export-document-entry.js +0 -3
- package/src/client/app/export-document.html +1 -0
- package/src/client/app/index.html +106 -24
- package/src/client/app/main-entry.js +0 -5
- package/src/client/application/app-shell/chat-feature.js +180 -124
- package/src/client/application/app-shell/comments-feature.js +20 -11
- package/src/client/application/app-shell/git-feature.js +16 -9
- package/src/client/application/app-shell/presence-feature.js +18 -11
- package/src/client/application/app-shell/ui-feature-shell.js +78 -17
- package/src/client/application/app-shell/ui-feature-sidebar.js +74 -1
- package/src/client/application/app-shell/ui-feature-tab-activity.js +2 -2
- package/src/client/application/app-shell/ui-feature-toolbar.js +2 -52
- package/src/client/application/app-shell/workspace-feature.js +27 -84
- package/src/client/application/app-shell-elements.js +9 -1
- package/src/client/application/diagram-chrome.js +161 -166
- package/src/client/application/diagram-preview-export.js +7 -34
- package/src/client/application/diagram-preview-hydrator.js +1 -19
- package/src/client/application/markdown-frontmatter.js +0 -2
- package/src/client/application/mermaid-preview-hydrator.js +1 -14
- package/src/client/application/pdf-preview-controller.js +992 -0
- package/src/client/application/plantuml-preview-hydrator.js +0 -10
- package/src/client/application/preview-diagram-utils.js +31 -2
- package/src/client/application/preview-render-compiler.js +2 -1
- package/src/client/application/preview-renderer.js +2 -2
- package/src/client/application/quick-switcher-loader.js +3 -1
- 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 -5
- package/src/client/bootstrap/collabmd-app-shell.js +155 -43
- package/src/client/browser-utils.js +44 -0
- package/src/client/domain/excalidraw-collaboration.js +0 -2
- package/src/client/domain/room.js +38 -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/export/export-pipeline.js +6 -34
- package/src/client/infrastructure/auth-client.js +4 -10
- package/src/client/infrastructure/browser-notification-port.js +33 -20
- package/src/client/infrastructure/browser-preferences-port.js +46 -9
- 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 +133 -5
- package/src/client/infrastructure/excalidraw-room-client.js +103 -14
- package/src/client/infrastructure/git-api-client.js +5 -0
- package/src/client/infrastructure/lobby-presence.js +1 -0
- package/src/client/infrastructure/vault-api-client.js +51 -0
- package/src/client/infrastructure/workspace-sync-client.js +13 -1
- package/src/client/presentation/comment-overview-controller.js +18 -53
- package/src/client/presentation/comment-ui/comment-ui-card.js +1 -12
- package/src/client/presentation/comment-ui/comment-ui-layout.js +242 -219
- package/src/client/presentation/comment-ui/comment-ui-render.js +32 -45
- package/src/client/presentation/comment-ui/comment-ui-shared.js +68 -13
- package/src/client/presentation/comment-ui-controller.js +5 -1
- 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 +134 -0
- package/src/client/presentation/file-explorer-controller.js +33 -3
- package/src/client/presentation/file-explorer-view.js +59 -34
- package/src/client/presentation/file-icon-svg.js +40 -0
- package/src/client/presentation/file-tree-state.js +12 -4
- 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 +257 -74
- package/src/client/presentation/quick-switcher-text-search.js +3 -3
- package/src/client/presentation/toast-controller.js +3 -0
- package/src/client/styles/components/feedback.css +6 -0
- package/src/client/styles/components/navigation.css +11 -1
- package/src/client/styles/features/collaboration-chat.css +49 -53
- package/src/client/styles/features/collaboration-presence.css +9 -7
- package/src/client/styles/features/comment-card.css +25 -11
- package/src/client/styles/features/comment-overlays.css +0 -4
- package/src/client/styles/features/comment-overview.css +44 -21
- package/src/client/styles/features/comments-drawer.css +2 -100
- package/src/client/styles/features/diagram-preview.css +81 -18
- package/src/client/styles/features/diff-special.css +309 -0
- package/src/client/styles/features/embedded-preview.css +8 -33
- package/src/client/styles/features/quick-switcher.css +8 -0
- package/src/client/styles/foundation/themes.css +6 -0
- package/src/client/styles/foundation/tokens.css +1 -0
- package/src/client/styles/layout/editor-page.css +40 -0
- package/src/client/styles/layout/responsive.css +31 -1
- package/src/client/styles/layout/shell.css +383 -7
- package/src/client/styles/layout/sidebar.css +47 -15
- package/src/client/styles/layout/view-modes.css +1 -0
- package/src/client/styles/overrides/codemirror.css +217 -15
- 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/application/workspace-reconciliation.js +1 -1
- package/src/server/config/env.js +79 -0
- package/src/server/create-app-server.js +14 -3
- 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-query-service.js +1 -77
- 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 +25 -8
- package/src/server/domain/docx-exporter.js +24 -33
- package/src/server/domain/ripgrep-search-service.js +7 -1
- package/src/server/domain/workspace-state.js +32 -11
- package/src/server/infrastructure/git/diff-service.js +29 -13
- package/src/server/infrastructure/git/git-service.js +61 -10
- package/src/server/infrastructure/git/history-service.js +96 -29
- package/src/server/infrastructure/git/image-mime.js +14 -0
- package/src/server/infrastructure/git/path-utils.js +1 -1
- package/src/server/infrastructure/git/responses.js +0 -95
- package/src/server/infrastructure/git/untracked-files.js +28 -1
- package/src/server/infrastructure/github/github-app-client.js +22 -15
- package/src/server/infrastructure/http/create-git-api-query-handler.js +31 -1
- package/src/server/infrastructure/http/create-request-handler.js +18 -31
- 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 +70 -8
- package/src/server/infrastructure/http/create-vault-api-query-handler.js +51 -3
- package/src/server/infrastructure/http/http-errors.js +7 -1
- 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/bases-preview-controller-2pLHk0uk.js +0 -143
- package/dist/client/assets/browser-utils-CKtI_rZl.js +0 -1
- package/dist/client/assets/button-BCEiw4Rw.js +0 -1
- package/dist/client/assets/chunk-AXR3VHVW-0GTk_evH.js +0 -41
- package/dist/client/assets/comment-threads-D-iKphIp.js +0 -4
- package/dist/client/assets/diagram-action-icons-aa_KOv9h.js +0 -36
- 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/drawio-editor-DDvFoarP.js +0 -9
- package/dist/client/assets/drawio-embed-controller-BD1GTRwn.js +0 -1
- package/dist/client/assets/drawioEditor-Bu5grlH5.js +0 -2
- package/dist/client/assets/editor-session-DGlpiAsK.js +0 -16
- package/dist/client/assets/embedded-editor-base-C9roKYpW.css +0 -1
- package/dist/client/assets/excalidraw-editor-BHu6k8d4.js +0 -9
- package/dist/client/assets/excalidraw-editor-Cr_184EF.css +0 -1
- package/dist/client/assets/excalidraw-embed-controller-y2fenN_g.js +0 -1
- package/dist/client/assets/excalidraw-embed-reconciler-DGiagExg.js +0 -1
- package/dist/client/assets/excalidraw-scene-BvsQ6mLP.js +0 -176
- package/dist/client/assets/excalidrawEditor-4vFE--Uw.js +0 -2
- package/dist/client/assets/export-page-Cby8JzPO.js +0 -39
- package/dist/client/assets/exportDocument-C9v-bc7I.css +0 -1
- package/dist/client/assets/exportDocument-CRHCfsdo.js +0 -2
- package/dist/client/assets/file-history-view-controller-BDjEV9xJ.js +0 -112
- package/dist/client/assets/file-kind-BKPOIRPE.js +0 -1
- package/dist/client/assets/git-diff-view-controller-DgdM1aqb.js +0 -150
- package/dist/client/assets/git-panel-controller-BDTR2HVp.js +0 -207
- package/dist/client/assets/image-XGD7JQR6-XDUWHRpO.js +0 -1
- package/dist/client/assets/index-7xOyXDhP.css +0 -1
- package/dist/client/assets/index-Bz1KuQ6y.js +0 -2
- package/dist/client/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/client/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/client/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/client/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/client/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/client/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/client/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/client/assets/main-Ci07m9oP.js +0 -152
- package/dist/client/assets/preview-diagram-utils-n_bbJA9V.js +0 -1
- package/dist/client/assets/preview-render-compiler-csGM5ZFz.js +0 -1
- package/dist/client/assets/quick-switcher-controller-CoiYvllm.js +0 -12
- package/dist/client/assets/room-CDrmIdDt.js +0 -1
- package/dist/client/assets/runtime-config-CDgeuPWN.js +0 -1
- package/dist/client/assets/runtime-config-loader-BHGniHP5.js +0 -1
- package/dist/client/assets/runtime-paths-DvpihCEq.js +0 -1
- package/dist/client/assets/segmented-control-DJrHSz8M.js +0 -1
- package/dist/client/assets/vault-api-client-sLB2bo56.js +0 -1
- package/dist/client/assets/vault-paths-CVkuh0j-.js +0 -5
- package/src/client/bootstrap/lazy-controller-feature.js +0 -433
- package/src/client/infrastructure/backlinks-api-client.js +0 -16
- package/src/client/infrastructure/plantuml-api-client.js +0 -19
- package/src/client/infrastructure/runtime-config-loader.js +0 -56
- package/src/server/domain/collaboration/room-persistence-controller.js +0 -80
- package/src/server/infrastructure/git/command-runner.js +0 -49
- package/src/server/infrastructure/git/errors.js +0 -8
- package/src/server/infrastructure/http/create-esm-proxy-handler.js +0 -130
|
@@ -2,12 +2,16 @@ import { createAuthApiHandler } from './create-auth-api-handler.js';
|
|
|
2
2
|
import { createGitApiCommandHandler } from './create-git-api-command-handler.js';
|
|
3
3
|
import { createGitApiQueryHandler } from './create-git-api-query-handler.js';
|
|
4
4
|
import { createHostedApiHandler } from './create-hosted-api-handler.js';
|
|
5
|
-
import { createEsmProxyHandler } from './create-esm-proxy-handler.js';
|
|
6
5
|
import { createPlantUmlApiHandler } from './create-plantuml-api-handler.js';
|
|
7
6
|
import { createStaticHandler } from './create-static-handler.js';
|
|
7
|
+
import { createStructurizrApiHandler } from './create-structurizr-api-handler.js';
|
|
8
8
|
import { createVaultApiCommandHandler } from './create-vault-api-command-handler.js';
|
|
9
9
|
import { createVaultApiQueryHandler } from './create-vault-api-query-handler.js';
|
|
10
10
|
import { parseJsonBody } from './request-body.js';
|
|
11
|
+
import {
|
|
12
|
+
createRequestUrlWithPathname,
|
|
13
|
+
stripBasePath,
|
|
14
|
+
} from './http-request-helpers.js';
|
|
11
15
|
import {
|
|
12
16
|
applyCorsHeaders,
|
|
13
17
|
jsonResponse,
|
|
@@ -17,35 +21,13 @@ import {
|
|
|
17
21
|
WRITE_METHODS,
|
|
18
22
|
} from './http-response.js';
|
|
19
23
|
|
|
20
|
-
function stripBasePath(pathname, basePath) {
|
|
21
|
-
if (!basePath) {
|
|
22
|
-
return pathname;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (pathname === basePath) {
|
|
26
|
-
return '/';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (pathname.startsWith(`${basePath}/`)) {
|
|
30
|
-
return pathname.slice(basePath.length) || '/';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return pathname;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function createRequestUrlWithPathname(requestUrl, pathname) {
|
|
37
|
-
const nextUrl = new URL(requestUrl.toString());
|
|
38
|
-
nextUrl.pathname = pathname || '/';
|
|
39
|
-
return nextUrl;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
24
|
export function createRequestHandler(
|
|
43
25
|
config,
|
|
44
26
|
authService,
|
|
45
27
|
vaultFileStore,
|
|
46
28
|
backlinkIndex,
|
|
47
29
|
baseQueryService = null,
|
|
48
|
-
|
|
30
|
+
renderDocx = null,
|
|
49
31
|
roomRegistry = null,
|
|
50
32
|
plantUmlRenderer = null,
|
|
51
33
|
gitService = null,
|
|
@@ -55,8 +37,8 @@ export function createRequestHandler(
|
|
|
55
37
|
fileSystemSyncService = null,
|
|
56
38
|
hostedWorkspaceService = null,
|
|
57
39
|
githubSetupFlow = null,
|
|
40
|
+
structurizrWorkspaceService = null,
|
|
58
41
|
) {
|
|
59
|
-
const handleEsmProxy = createEsmProxyHandler();
|
|
60
42
|
const handleStaticRequest = createStaticHandler(config, authService, searchService);
|
|
61
43
|
const handleAuthApi = createAuthApiHandler({ authService });
|
|
62
44
|
const handleHostedApi = createHostedApiHandler({
|
|
@@ -80,12 +62,17 @@ export function createRequestHandler(
|
|
|
80
62
|
});
|
|
81
63
|
const handleVaultApiCommand = createVaultApiCommandHandler({
|
|
82
64
|
backlinkIndex,
|
|
83
|
-
|
|
65
|
+
maxPdfUploadBytes: config.maxPdfUploadBytes,
|
|
66
|
+
renderDocx,
|
|
84
67
|
roomRegistry,
|
|
85
68
|
vaultFileStore,
|
|
86
69
|
workspaceMutationCoordinator,
|
|
87
70
|
});
|
|
88
71
|
const handlePlantUmlApi = createPlantUmlApiHandler({ plantUmlRenderer });
|
|
72
|
+
const handleStructurizrApi = createStructurizrApiHandler({
|
|
73
|
+
basePath: config.basePath,
|
|
74
|
+
service: structurizrWorkspaceService,
|
|
75
|
+
});
|
|
89
76
|
|
|
90
77
|
function handleBasePathRedirect(req, res, originalRequestUrl) {
|
|
91
78
|
if (
|
|
@@ -195,11 +182,7 @@ export function createRequestHandler(
|
|
|
195
182
|
return;
|
|
196
183
|
}
|
|
197
184
|
|
|
198
|
-
if (
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (requestUrl.pathname.startsWith('/api/')) {
|
|
185
|
+
if (requestUrl.pathname.startsWith('/api/') || handleStructurizrApi.requiresAuthorization(requestUrl)) {
|
|
203
186
|
const authorization = authService.authorizeApiRequest(req);
|
|
204
187
|
if (!authorization.ok) {
|
|
205
188
|
jsonResponse(req, res, authorization.statusCode, authorization.body);
|
|
@@ -215,6 +198,10 @@ export function createRequestHandler(
|
|
|
215
198
|
}
|
|
216
199
|
}
|
|
217
200
|
|
|
201
|
+
if (await handleStructurizrApi(req, res, requestUrl)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
218
205
|
if (requestUrl.pathname.startsWith('/api/')) {
|
|
219
206
|
if (await handleVaultApiQuery(req, res, requestUrl)) {
|
|
220
207
|
return;
|
|
@@ -7,12 +7,13 @@ const CONTENT_TYPES = {
|
|
|
7
7
|
'.css': 'text/css; charset=utf-8',
|
|
8
8
|
'.html': 'text/html; charset=utf-8',
|
|
9
9
|
'.js': 'text/javascript; charset=utf-8',
|
|
10
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
10
11
|
'.json': 'application/json; charset=utf-8',
|
|
11
12
|
'.svg': 'image/svg+xml',
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
function isVersionedAssetPath(pathname = '') {
|
|
15
|
-
return /-[A-Za-z0-9_-]{8,}\.(?:css|js|woff2?|ttf|svg)$/u.test(pathname);
|
|
16
|
+
return /-[A-Za-z0-9_-]{8,}\.(?:css|js|mjs|woff2?|ttf|svg)$/u.test(pathname);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function getStaticCacheControl(pathname = '', extension = '') {
|
|
@@ -33,7 +34,7 @@ function getStaticCacheControl(pathname = '', extension = '') {
|
|
|
33
34
|
return 'public, max-age=31536000, immutable';
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
if (extension === '.css' || extension === '.js') {
|
|
37
|
+
if (extension === '.css' || extension === '.js' || extension === '.mjs') {
|
|
37
38
|
return 'public, max-age=0, must-revalidate';
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -53,6 +54,7 @@ function buildRuntimeConfig({
|
|
|
53
54
|
nodeEnv,
|
|
54
55
|
publicWsBaseUrl,
|
|
55
56
|
search,
|
|
57
|
+
structurizr,
|
|
56
58
|
wikiLinkAutoCreate,
|
|
57
59
|
wsBasePath,
|
|
58
60
|
}) {
|
|
@@ -68,6 +70,7 @@ function buildRuntimeConfig({
|
|
|
68
70
|
},
|
|
69
71
|
publicWsBaseUrl,
|
|
70
72
|
search,
|
|
73
|
+
structurizrEnabled: Boolean(structurizr?.enabled ?? structurizr?.serverUrl),
|
|
71
74
|
wikiLinkAutoCreate,
|
|
72
75
|
wsBasePath,
|
|
73
76
|
})};\n`;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import { getRequestErrorStatusCode } from './http-errors.js';
|
|
4
|
+
import { jsonResponse, sendResponse, textResponse } from './http-response.js';
|
|
5
|
+
import { parseJsonBody } from './request-body.js';
|
|
6
|
+
|
|
7
|
+
const EMBED_STYLESHEET_PATH = new URL('../structurizr/structurizr-embed.css', import.meta.url);
|
|
8
|
+
|
|
9
|
+
function isSyncRequest(requestUrl, req) {
|
|
10
|
+
return requestUrl.pathname === '/api/structurizr/sync' && req.method === 'POST';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isEmbedStylesheetRequest(requestUrl) {
|
|
14
|
+
return requestUrl.pathname === '/api/structurizr/embed.css';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function rewriteBasePath(body, contentType, basePath, { injectEmbedStylesheet = false } = {}) {
|
|
18
|
+
if (!/(?:text\/(?:html|css)|javascript|ecmascript)/iu.test(contentType)) {
|
|
19
|
+
return body;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let source = body.toString('utf8');
|
|
23
|
+
if (basePath) {
|
|
24
|
+
source = source.replace(
|
|
25
|
+
/(["'`(=])\/(static|workspace|api\/workspace)(?=[/"'`?#)])/gu,
|
|
26
|
+
`$1${basePath}/$2`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (injectEmbedStylesheet && /text\/html/iu.test(contentType)) {
|
|
31
|
+
const normalizedBasePath = String(basePath || '').replace(/\/+$/u, '');
|
|
32
|
+
const stylesheetPath = `${normalizedBasePath}/api/structurizr/embed.css`;
|
|
33
|
+
source = source.replace(
|
|
34
|
+
/<head(?:\s[^>]*)?>/iu,
|
|
35
|
+
(head) => `${head}\n<link rel="stylesheet" href="${stylesheetPath}">`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return Buffer.from(source);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createStructurizrApiHandler({ basePath = '', service = null } = {}) {
|
|
43
|
+
async function handleRequest(req, res, requestUrl) {
|
|
44
|
+
if (isSyncRequest(requestUrl, req)) {
|
|
45
|
+
try {
|
|
46
|
+
const body = await parseJsonBody(req);
|
|
47
|
+
if (!body || typeof body.path !== 'string') {
|
|
48
|
+
jsonResponse(req, res, 400, { error: 'Missing Structurizr workspace path' });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!service?.isEnabled()) {
|
|
53
|
+
jsonResponse(req, res, 503, { error: 'Structurizr renderer is not configured' });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
jsonResponse(req, res, 200, await service.sync({
|
|
58
|
+
content: body.source,
|
|
59
|
+
rootPath: body.path,
|
|
60
|
+
}));
|
|
61
|
+
} catch (error) {
|
|
62
|
+
const statusCode = getRequestErrorStatusCode(error) || Number(error?.statusCode) || 502;
|
|
63
|
+
if (statusCode >= 500) {
|
|
64
|
+
console.error('[api] Failed to sync Structurizr workspace:', error.message);
|
|
65
|
+
}
|
|
66
|
+
jsonResponse(req, res, statusCode, {
|
|
67
|
+
code: error?.requestCode,
|
|
68
|
+
error: error instanceof Error ? error.message : 'Failed to sync Structurizr workspace',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isEmbedStylesheetRequest(requestUrl)) {
|
|
75
|
+
if (!service?.isEnabled()) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
80
|
+
textResponse(req, res, 405, 'Method Not Allowed', { Allow: 'GET, HEAD' });
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
sendResponse(req, res, {
|
|
86
|
+
body: await readFile(EMBED_STYLESHEET_PATH),
|
|
87
|
+
headers: {
|
|
88
|
+
'Cache-Control': 'no-store',
|
|
89
|
+
'Content-Type': 'text/css; charset=utf-8',
|
|
90
|
+
},
|
|
91
|
+
statusCode: 200,
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('[http] Failed to serve Structurizr embed stylesheet:', error.message);
|
|
95
|
+
textResponse(req, res, 500, 'Internal Server Error');
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!service?.isProxyPath(requestUrl.pathname)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
105
|
+
textResponse(req, res, 405, 'Method Not Allowed', { Allow: 'GET, HEAD' });
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const response = await service.proxy(requestUrl, {
|
|
111
|
+
headers: req.headers,
|
|
112
|
+
method: req.method,
|
|
113
|
+
});
|
|
114
|
+
if (!response) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
sendResponse(req, res, {
|
|
119
|
+
body: rewriteBasePath(response.body, response.contentType, basePath, {
|
|
120
|
+
injectEmbedStylesheet: requestUrl.pathname === '/workspace/1/diagrams',
|
|
121
|
+
}),
|
|
122
|
+
headers: {
|
|
123
|
+
'Cache-Control': requestUrl.pathname.startsWith('/static/')
|
|
124
|
+
? 'public, max-age=31536000, immutable'
|
|
125
|
+
: 'no-store',
|
|
126
|
+
'Content-Type': response.contentType,
|
|
127
|
+
},
|
|
128
|
+
statusCode: response.statusCode,
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
const statusCode = getRequestErrorStatusCode(error) || Number(error?.statusCode) || 502;
|
|
132
|
+
console.error('[http] Failed to proxy Structurizr request:', error.message);
|
|
133
|
+
textResponse(req, res, statusCode, error.message || 'Structurizr renderer unavailable');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
handleRequest.requiresAuthorization = (requestUrl) => (
|
|
140
|
+
Boolean(service?.isProxyPath(requestUrl.pathname))
|
|
141
|
+
&& !requestUrl.pathname.startsWith('/api/')
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return handleRequest;
|
|
145
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { basename } from 'node:path';
|
|
2
|
+
import { isPdfFilePath } from '../../../domain/file-kind.js';
|
|
2
3
|
import { createRequestError } from './http-errors.js';
|
|
3
4
|
import { handleApiError, readRequestId } from './http-request-helpers.js';
|
|
4
5
|
import {
|
|
@@ -7,10 +8,15 @@ import {
|
|
|
7
8
|
jsonResponse,
|
|
8
9
|
sendResponse,
|
|
9
10
|
} from './http-response.js';
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
parseJsonBody,
|
|
13
|
+
readBinaryRequestBody,
|
|
14
|
+
REQUEST_BODY_LIMIT_BYTES,
|
|
15
|
+
} from './request-body.js';
|
|
11
16
|
|
|
12
17
|
const DOCX_EXPORT_REQUEST_LIMIT_BYTES = 33_554_432;
|
|
13
18
|
const DOCX_MIME_TYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
|
19
|
+
const PDF_SIGNATURE = Buffer.from('%PDF-');
|
|
14
20
|
|
|
15
21
|
function createDocxDownloadHeaders(filePath) {
|
|
16
22
|
const fileName = basename(String(filePath ?? 'document')).replace(/\.[^.]+$/u, '') || 'document';
|
|
@@ -23,7 +29,7 @@ function createDocxDownloadHeaders(filePath) {
|
|
|
23
29
|
};
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
function decodeHeaderMetadata(value) {
|
|
32
|
+
function decodeHeaderMetadata(value, label = 'attachment') {
|
|
27
33
|
const normalized = String(value ?? '').trim();
|
|
28
34
|
if (!normalized) {
|
|
29
35
|
return '';
|
|
@@ -32,7 +38,7 @@ function decodeHeaderMetadata(value) {
|
|
|
32
38
|
try {
|
|
33
39
|
return decodeURIComponent(normalized);
|
|
34
40
|
} catch {
|
|
35
|
-
throw createRequestError(400,
|
|
41
|
+
throw createRequestError(400, `Invalid ${label} metadata header encoding`);
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
|
|
@@ -40,7 +46,7 @@ function getDirectoryDeleteStatusCode(message = '') {
|
|
|
40
46
|
return String(message).includes('Directory is not empty') ? 409 : 400;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
async function handleExportDocx({
|
|
49
|
+
async function handleExportDocx({ renderDocx }, req, res) {
|
|
44
50
|
try {
|
|
45
51
|
const body = await parseJsonBody(req, DOCX_EXPORT_REQUEST_LIMIT_BYTES);
|
|
46
52
|
if (!body?.filePath || typeof body?.html !== 'string') {
|
|
@@ -48,12 +54,12 @@ async function handleExportDocx({ docxExporter }, req, res) {
|
|
|
48
54
|
return true;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
if (
|
|
57
|
+
if (typeof renderDocx !== 'function') {
|
|
52
58
|
jsonResponse(req, res, 503, { error: 'DOCX export is unavailable' });
|
|
53
59
|
return true;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
|
-
const docxBuffer = await
|
|
62
|
+
const docxBuffer = await renderDocx({
|
|
57
63
|
html: body.html,
|
|
58
64
|
title: body.title || '',
|
|
59
65
|
});
|
|
@@ -129,6 +135,55 @@ async function handleUploadAttachment({ workspaceMutationCoordinator }, req, res
|
|
|
129
135
|
return true;
|
|
130
136
|
}
|
|
131
137
|
|
|
138
|
+
async function handleUploadFile({ maxPdfUploadBytes, workspaceMutationCoordinator }, req, res) {
|
|
139
|
+
try {
|
|
140
|
+
const filePath = decodeHeaderMetadata(req.headers['x-collabmd-file-path'], 'file upload');
|
|
141
|
+
if (!filePath) {
|
|
142
|
+
jsonResponse(req, res, 400, { error: 'Missing file path' });
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (typeof workspaceMutationCoordinator?.createFile !== 'function') {
|
|
147
|
+
jsonResponse(req, res, 503, { error: 'File upload is unavailable' });
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const isPdfUpload = isPdfFilePath(filePath);
|
|
152
|
+
if (isPdfUpload) {
|
|
153
|
+
const contentType = String(req.headers['content-type'] || '')
|
|
154
|
+
.split(';', 1)[0]
|
|
155
|
+
.trim()
|
|
156
|
+
.toLowerCase();
|
|
157
|
+
if (contentType !== 'application/pdf') {
|
|
158
|
+
jsonResponse(req, res, 400, { error: 'PDF uploads must use application/pdf' });
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const maxUploadBytes = isPdfUpload ? maxPdfUploadBytes : REQUEST_BODY_LIMIT_BYTES;
|
|
164
|
+
const content = await readBinaryRequestBody(req, maxUploadBytes);
|
|
165
|
+
if (isPdfUpload && !content.subarray(0, PDF_SIGNATURE.length).equals(PDF_SIGNATURE)) {
|
|
166
|
+
jsonResponse(req, res, 400, { error: 'Invalid PDF file' });
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const result = await workspaceMutationCoordinator.createFile({
|
|
171
|
+
content,
|
|
172
|
+
path: filePath,
|
|
173
|
+
requestId: readRequestId(req),
|
|
174
|
+
});
|
|
175
|
+
if (!result.ok) {
|
|
176
|
+
jsonResponse(req, res, result.error === 'File already exists' ? 409 : 400, { error: result.error });
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
jsonResponse(req, res, 201, { ok: true, path: filePath });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
handleApiError(req, res, error, '[api] Failed to upload file:', 'Failed to upload file');
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
132
187
|
async function handleCreateFile({ workspaceMutationCoordinator }, req, res) {
|
|
133
188
|
try {
|
|
134
189
|
const body = await parseJsonBody(req);
|
|
@@ -280,6 +335,7 @@ const ROUTE_TABLE = [
|
|
|
280
335
|
{ method: 'POST', path: '/api/export/docx', handler: handleExportDocx },
|
|
281
336
|
{ method: 'PUT', path: '/api/file', handler: handleWriteFile },
|
|
282
337
|
{ method: 'POST', path: '/api/attachments', handler: handleUploadAttachment },
|
|
338
|
+
{ method: 'POST', path: '/api/file/upload', handler: handleUploadFile },
|
|
283
339
|
{ method: 'POST', path: '/api/file', handler: handleCreateFile },
|
|
284
340
|
{ method: 'DELETE', path: '/api/file', handler: handleDeleteFile },
|
|
285
341
|
{ method: 'PATCH', path: '/api/file', handler: handleRenameFile },
|
|
@@ -289,11 +345,17 @@ const ROUTE_TABLE = [
|
|
|
289
345
|
];
|
|
290
346
|
|
|
291
347
|
export function createVaultApiCommandHandler({
|
|
292
|
-
|
|
348
|
+
maxPdfUploadBytes = REQUEST_BODY_LIMIT_BYTES,
|
|
349
|
+
renderDocx = null,
|
|
293
350
|
vaultFileStore,
|
|
294
351
|
workspaceMutationCoordinator = null,
|
|
295
352
|
}) {
|
|
296
|
-
const context = {
|
|
353
|
+
const context = {
|
|
354
|
+
maxPdfUploadBytes,
|
|
355
|
+
renderDocx,
|
|
356
|
+
vaultFileStore,
|
|
357
|
+
workspaceMutationCoordinator,
|
|
358
|
+
};
|
|
297
359
|
|
|
298
360
|
return async function handleVaultApiCommand(req, res, requestUrl) {
|
|
299
361
|
for (const route of ROUTE_TABLE) {
|
|
@@ -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,12 @@
|
|
|
1
|
-
export function createRequestError(statusCode, message) {
|
|
1
|
+
export function createRequestError(statusCode, message, { code = null, requestCode = null } = {}) {
|
|
2
2
|
const error = new Error(message);
|
|
3
3
|
error.statusCode = statusCode;
|
|
4
|
+
if (code) {
|
|
5
|
+
error.code = code;
|
|
6
|
+
}
|
|
7
|
+
if (requestCode) {
|
|
8
|
+
error.requestCode = requestCode;
|
|
9
|
+
}
|
|
4
10
|
return error;
|
|
5
11
|
}
|
|
6
12
|
|
|
@@ -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
|
+
}
|