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
|
@@ -9,6 +9,8 @@ export class OutlineController {
|
|
|
9
9
|
this.activeHeadingId = null;
|
|
10
10
|
this.headings = [];
|
|
11
11
|
this.pinnedHeadingId = null;
|
|
12
|
+
this.pdfOutline = null;
|
|
13
|
+
this.onNavigateToPdfOutline = null;
|
|
12
14
|
this.onNavigateToHeading = onNavigateToHeading;
|
|
13
15
|
this.onWillOpen = onWillOpen;
|
|
14
16
|
this.panel = document.getElementById('outlinePanel');
|
|
@@ -81,6 +83,11 @@ export class OutlineController {
|
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
if (this.pdfOutline) {
|
|
87
|
+
this.renderPdfOutline();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
84
91
|
const previewContent = document.getElementById('previewContent');
|
|
85
92
|
const headings = previewContent?.querySelectorAll('h1, h2, h3, h4, h5, h6') ?? [];
|
|
86
93
|
|
|
@@ -124,6 +131,85 @@ export class OutlineController {
|
|
|
124
131
|
this.observeHeadings(Array.from(headings));
|
|
125
132
|
}
|
|
126
133
|
|
|
134
|
+
setPdfOutline(outline, onNavigate) {
|
|
135
|
+
this.cleanup();
|
|
136
|
+
this.pdfOutline = Array.isArray(outline) && outline.length > 0 ? outline : null;
|
|
137
|
+
this.onNavigateToPdfOutline = this.pdfOutline ? onNavigate : null;
|
|
138
|
+
this.activeHeadingId = null;
|
|
139
|
+
this.toggleButton?.classList.toggle('hidden', !this.pdfOutline);
|
|
140
|
+
|
|
141
|
+
if (!this.pdfOutline) {
|
|
142
|
+
this.navigation?.replaceChildren();
|
|
143
|
+
this.close();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (this.outlineOpen) {
|
|
148
|
+
this.renderPdfOutline();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
clearPdfOutline() {
|
|
153
|
+
this.pdfOutline = null;
|
|
154
|
+
this.onNavigateToPdfOutline = null;
|
|
155
|
+
this.activeHeadingId = null;
|
|
156
|
+
this.pinnedHeadingId = null;
|
|
157
|
+
this.navigation?.replaceChildren();
|
|
158
|
+
this.toggleButton?.classList.add('hidden');
|
|
159
|
+
this.close();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
renderPdfOutline() {
|
|
163
|
+
if (!this.navigation || !this.pdfOutline) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.cleanup();
|
|
168
|
+
this.navigation.replaceChildren();
|
|
169
|
+
const fragment = document.createDocumentFragment();
|
|
170
|
+
let itemIndex = 0;
|
|
171
|
+
const appendItems = (items, level = 1) => {
|
|
172
|
+
items.forEach((item) => {
|
|
173
|
+
if (!item || typeof item !== 'object') {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const title = String(item.title ?? '').trim();
|
|
178
|
+
if (title) {
|
|
179
|
+
const id = `pdf-outline-${itemIndex}`;
|
|
180
|
+
const button = document.createElement('button');
|
|
181
|
+
button.type = 'button';
|
|
182
|
+
button.className = 'outline-item';
|
|
183
|
+
button.dataset.level = String(Math.min(level, 6));
|
|
184
|
+
button.dataset.target = id;
|
|
185
|
+
button.title = title;
|
|
186
|
+
button.textContent = title;
|
|
187
|
+
button.addEventListener('click', () => {
|
|
188
|
+
this.setActiveItem(id, { behavior: 'auto' });
|
|
189
|
+
this.onNavigateToPdfOutline?.(item);
|
|
190
|
+
if (this.mobileBreakpointQuery.matches) {
|
|
191
|
+
this.close();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
fragment.appendChild(button);
|
|
195
|
+
itemIndex += 1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (Array.isArray(item.items)) {
|
|
199
|
+
appendItems(item.items, Math.min(level + 1, 6));
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
appendItems(this.pdfOutline);
|
|
205
|
+
if (fragment.childNodes.length === 0) {
|
|
206
|
+
this.navigation.innerHTML = '<div class="outline-empty">No outline found</div>';
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this.navigation.appendChild(fragment);
|
|
211
|
+
}
|
|
212
|
+
|
|
127
213
|
cleanup({ preservePinnedHeading = false } = {}) {
|
|
128
214
|
if (this.activeHeadingFrame) {
|
|
129
215
|
cancelAnimationFrame(this.activeHeadingFrame);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { stripVaultFileExtension } from '../../domain/file-kind.js';
|
|
2
2
|
import { escapeHtml } from '../domain/vault-utils.js';
|
|
3
|
+
import { getVaultFileIconSvg } from './file-icon-svg.js';
|
|
3
4
|
import {
|
|
4
5
|
DEFAULT_SEARCH_DEBOUNCE_MS,
|
|
5
6
|
QuickSwitcherTextSearchRunner,
|
|
@@ -7,11 +8,19 @@ import {
|
|
|
7
8
|
formatMatchCount,
|
|
8
9
|
} from './quick-switcher-text-search.js';
|
|
9
10
|
|
|
10
|
-
const MAX_VISIBLE_RESULTS =
|
|
11
|
+
const MAX_VISIBLE_RESULTS = 30;
|
|
12
|
+
const NO_RECENT_FILE_RANK = Number.MAX_SAFE_INTEGER;
|
|
13
|
+
const FILE_RESULT_ID_PREFIX = 'quick-switcher-file-';
|
|
14
|
+
const TEXT_RESULT_ID_PREFIX = 'quick-switcher-text-';
|
|
15
|
+
|
|
16
|
+
function getRawFileName(filePath) {
|
|
17
|
+
return String(filePath ?? '').split('/').pop() || String(filePath ?? '');
|
|
18
|
+
}
|
|
11
19
|
|
|
12
20
|
function createCorpusEntry(filePath) {
|
|
13
21
|
const displayName = stripVaultFileExtension(filePath);
|
|
14
22
|
const fileName = displayName.split('/').pop() || displayName;
|
|
23
|
+
const rawFileName = getRawFileName(filePath);
|
|
15
24
|
|
|
16
25
|
return {
|
|
17
26
|
displayName,
|
|
@@ -20,6 +29,7 @@ function createCorpusEntry(filePath) {
|
|
|
20
29
|
lowerDisplayName: displayName.toLowerCase(),
|
|
21
30
|
lowerFileName: fileName.toLowerCase(),
|
|
22
31
|
lowerPath: String(filePath).toLowerCase(),
|
|
32
|
+
lowerRawFileName: rawFileName.toLowerCase(),
|
|
23
33
|
};
|
|
24
34
|
}
|
|
25
35
|
|
|
@@ -33,9 +43,135 @@ function getDirPath(filePath) {
|
|
|
33
43
|
return displayName.includes('/') ? displayName.substring(0, displayName.lastIndexOf('/')) : '';
|
|
34
44
|
}
|
|
35
45
|
|
|
46
|
+
function createRangeIndices(start, length) {
|
|
47
|
+
return Array.from({ length }, (_, index) => start + index);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function findFuzzyMatch(text, query) {
|
|
51
|
+
const indices = [];
|
|
52
|
+
let queryIndex = 0;
|
|
53
|
+
let score = 0;
|
|
54
|
+
let consecutiveBonus = 0;
|
|
55
|
+
|
|
56
|
+
for (let index = 0; index < text.length && queryIndex < query.length; index += 1) {
|
|
57
|
+
if (text[index] !== query[queryIndex]) {
|
|
58
|
+
consecutiveBonus = 0;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
queryIndex += 1;
|
|
63
|
+
indices.push(index);
|
|
64
|
+
consecutiveBonus += 1;
|
|
65
|
+
score += consecutiveBonus;
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
index === 0
|
|
69
|
+
|| text[index - 1] === '/'
|
|
70
|
+
|| text[index - 1] === '-'
|
|
71
|
+
|| text[index - 1] === '_'
|
|
72
|
+
|| text[index - 1] === ' '
|
|
73
|
+
) {
|
|
74
|
+
score += 5;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return queryIndex === query.length && score > 0
|
|
79
|
+
? { indices, score }
|
|
80
|
+
: null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function findCorpusMatch(entry, query) {
|
|
84
|
+
const fileIndex = entry.lowerFileName.indexOf(query);
|
|
85
|
+
if (fileIndex >= 0) {
|
|
86
|
+
return {
|
|
87
|
+
indices: createRangeIndices(fileIndex + (entry.displayName.length - entry.fileName.length), query.length),
|
|
88
|
+
score: 100 + (1 / entry.fileName.length),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const pathIndex = entry.lowerDisplayName.indexOf(query);
|
|
93
|
+
if (pathIndex >= 0) {
|
|
94
|
+
return {
|
|
95
|
+
indices: createRangeIndices(pathIndex, query.length),
|
|
96
|
+
score: 50 + (1 / entry.displayName.length),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const rawFileIndex = entry.lowerRawFileName.indexOf(query);
|
|
101
|
+
if (rawFileIndex >= 0) {
|
|
102
|
+
return {
|
|
103
|
+
indices: createRangeIndices(
|
|
104
|
+
rawFileIndex + (entry.displayName.length - entry.fileName.length),
|
|
105
|
+
query.length,
|
|
106
|
+
),
|
|
107
|
+
score: 40 + (1 / entry.lowerRawFileName.length),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const rawPathIndex = entry.lowerPath.indexOf(query);
|
|
112
|
+
if (rawPathIndex >= 0) {
|
|
113
|
+
return {
|
|
114
|
+
indices: createRangeIndices(rawPathIndex, query.length),
|
|
115
|
+
score: 25 + (1 / entry.lowerPath.length),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return findFuzzyMatch(entry.lowerDisplayName, query);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function splitMatchIndices(entry, indices = []) {
|
|
123
|
+
const fileNameStart = entry.displayName.length - entry.fileName.length;
|
|
124
|
+
return {
|
|
125
|
+
dirPath: indices.filter((index) => index < fileNameStart),
|
|
126
|
+
fileName: indices
|
|
127
|
+
.filter((index) => index >= fileNameStart)
|
|
128
|
+
.map((index) => index - fileNameStart),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function highlightText(text, indices = []) {
|
|
133
|
+
if (indices.length === 0) {
|
|
134
|
+
return escapeHtml(text);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const matchedIndices = new Set(indices);
|
|
138
|
+
let result = '';
|
|
139
|
+
let runStart = null;
|
|
140
|
+
let cursor = 0;
|
|
141
|
+
|
|
142
|
+
const appendRun = (runEnd) => {
|
|
143
|
+
result += escapeHtml(text.slice(cursor, runStart));
|
|
144
|
+
result += `<mark>${escapeHtml(text.slice(runStart, runEnd + 1))}</mark>`;
|
|
145
|
+
cursor = runEnd + 1;
|
|
146
|
+
runStart = null;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
150
|
+
if (!matchedIndices.has(index)) {
|
|
151
|
+
if (runStart !== null) {
|
|
152
|
+
appendRun(index - 1);
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (runStart === null) {
|
|
158
|
+
runStart = index;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (runStart !== null) {
|
|
163
|
+
appendRun(text.length - 1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
result += escapeHtml(text.slice(cursor));
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
|
|
36
170
|
export class QuickSwitcherController {
|
|
37
171
|
constructor({
|
|
38
172
|
getFileList,
|
|
173
|
+
getFileMetadata = () => [],
|
|
174
|
+
getRecentFiles = () => [],
|
|
39
175
|
getSearchConfig = () => ({}),
|
|
40
176
|
onFileSelect,
|
|
41
177
|
onTextMatchSelect = null,
|
|
@@ -43,6 +179,8 @@ export class QuickSwitcherController {
|
|
|
43
179
|
searchText = null,
|
|
44
180
|
}) {
|
|
45
181
|
this.getFileList = getFileList;
|
|
182
|
+
this.getFileMetadata = getFileMetadata;
|
|
183
|
+
this.getRecentFiles = getRecentFiles;
|
|
46
184
|
this.getSearchConfig = getSearchConfig;
|
|
47
185
|
this.onFileSelect = onFileSelect;
|
|
48
186
|
this.onTextMatchSelect = onTextMatchSelect;
|
|
@@ -53,14 +191,19 @@ export class QuickSwitcherController {
|
|
|
53
191
|
this.input = document.getElementById('quickSwitcherInput');
|
|
54
192
|
this.resultsList = document.getElementById('quickSwitcherResults');
|
|
55
193
|
this.hint = document.getElementById('quickSwitcherHint');
|
|
194
|
+
this.scope = document.getElementById('quickSwitcherScope');
|
|
56
195
|
this.modeTabs = Array.from(document.querySelectorAll?.('[data-qs-mode]') ?? []);
|
|
57
196
|
|
|
58
197
|
this.filteredFiles = [];
|
|
198
|
+
this.fileMatches = new Map();
|
|
199
|
+
this.fileMatchCount = 0;
|
|
200
|
+
this.fileResultsTruncated = false;
|
|
59
201
|
this.fileCorpus = [];
|
|
60
202
|
this.lastFileListRef = null;
|
|
61
203
|
this.selectedIndex = 0;
|
|
62
204
|
this.selectedTextIndex = 0;
|
|
63
205
|
this.isOpen = false;
|
|
206
|
+
this.previousActiveElement = null;
|
|
64
207
|
this.mode = 'files';
|
|
65
208
|
this.textResults = null;
|
|
66
209
|
this.textResultItems = [];
|
|
@@ -119,11 +262,13 @@ export class QuickSwitcherController {
|
|
|
119
262
|
open() {
|
|
120
263
|
if (!this.overlay) return;
|
|
121
264
|
|
|
265
|
+
this.previousActiveElement = document.activeElement;
|
|
122
266
|
this.isOpen = true;
|
|
123
267
|
this.input.value = '';
|
|
124
268
|
this.selectedIndex = 0;
|
|
125
269
|
this.selectedTextIndex = 0;
|
|
126
270
|
this.overlay.classList.add('visible');
|
|
271
|
+
this.overlay.setAttribute('aria-hidden', 'false');
|
|
127
272
|
this.setMode('files', { preserveInput: true });
|
|
128
273
|
|
|
129
274
|
// The overlay transitions visibility hidden→visible over 120ms.
|
|
@@ -171,15 +316,23 @@ export class QuickSwitcherController {
|
|
|
171
316
|
}
|
|
172
317
|
}
|
|
173
318
|
|
|
174
|
-
close() {
|
|
319
|
+
close({ restoreFocus = true } = {}) {
|
|
175
320
|
if (!this.overlay) return;
|
|
176
321
|
|
|
322
|
+
const previousActiveElement = this.previousActiveElement;
|
|
177
323
|
this._cancelPendingFocus();
|
|
178
324
|
this.abortTextSearch();
|
|
179
325
|
this.isOpen = false;
|
|
180
326
|
this.overlay.classList.remove('visible');
|
|
327
|
+
this.overlay.setAttribute('aria-hidden', 'true');
|
|
181
328
|
this.input.value = '';
|
|
182
329
|
this.resultsList.innerHTML = '';
|
|
330
|
+
this.setActiveDescendant('');
|
|
331
|
+
this.previousActiveElement = null;
|
|
332
|
+
|
|
333
|
+
if (restoreFocus) {
|
|
334
|
+
previousActiveElement?.focus?.();
|
|
335
|
+
}
|
|
183
336
|
}
|
|
184
337
|
|
|
185
338
|
toggle() {
|
|
@@ -206,10 +359,17 @@ export class QuickSwitcherController {
|
|
|
206
359
|
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
207
360
|
});
|
|
208
361
|
|
|
362
|
+
this.scope?.classList.toggle('hidden', normalizedMode !== 'text');
|
|
363
|
+
this.scope?.setAttribute('aria-hidden', normalizedMode === 'text' ? 'false' : 'true');
|
|
364
|
+
|
|
209
365
|
if (this.input) {
|
|
210
366
|
this.input.placeholder = normalizedMode === 'text'
|
|
211
367
|
? 'Search text in files...'
|
|
212
368
|
: 'Search files...';
|
|
369
|
+
this.input.setAttribute(
|
|
370
|
+
'aria-label',
|
|
371
|
+
normalizedMode === 'text' ? 'Search text in files' : 'Search files',
|
|
372
|
+
);
|
|
213
373
|
}
|
|
214
374
|
|
|
215
375
|
this.handleInput();
|
|
@@ -233,23 +393,63 @@ export class QuickSwitcherController {
|
|
|
233
393
|
this.fileCorpus = allFiles.map((filePath) => createCorpusEntry(filePath));
|
|
234
394
|
}
|
|
235
395
|
|
|
396
|
+
this.fileMatches.clear();
|
|
397
|
+
const fileMetadata = this.getFileMetadata?.() ?? [];
|
|
398
|
+
const modifiedTimes = new Map(
|
|
399
|
+
(Array.isArray(fileMetadata) ? fileMetadata : []).map((entry) => [
|
|
400
|
+
entry?.path,
|
|
401
|
+
Number.isFinite(Number(entry?.mtimeMs)) ? Number(entry.mtimeMs) : 0,
|
|
402
|
+
]),
|
|
403
|
+
);
|
|
404
|
+
const recentFiles = this.getRecentFiles?.() ?? [];
|
|
405
|
+
const recentRanks = new Map(
|
|
406
|
+
(Array.isArray(recentFiles) ? recentFiles : []).map((filePath, index) => [filePath, index]),
|
|
407
|
+
);
|
|
236
408
|
if (!query) {
|
|
237
|
-
this.filteredFiles = this.fileCorpus
|
|
409
|
+
this.filteredFiles = [...this.fileCorpus]
|
|
410
|
+
.sort((left, right) => {
|
|
411
|
+
const modifiedDelta = (modifiedTimes.get(right.filePath) ?? 0) - (modifiedTimes.get(left.filePath) ?? 0);
|
|
412
|
+
if (modifiedDelta !== 0) {
|
|
413
|
+
return modifiedDelta;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return (recentRanks.get(left.filePath) ?? NO_RECENT_FILE_RANK)
|
|
417
|
+
- (recentRanks.get(right.filePath) ?? NO_RECENT_FILE_RANK);
|
|
418
|
+
})
|
|
238
419
|
.slice(0, MAX_VISIBLE_RESULTS)
|
|
239
420
|
.map((entry) => entry.filePath);
|
|
421
|
+
this.fileMatchCount = this.fileCorpus.length;
|
|
240
422
|
} else {
|
|
241
423
|
const ranked = [];
|
|
424
|
+
let fileMatchCount = 0;
|
|
242
425
|
this.fileCorpus.forEach((entry) => {
|
|
243
|
-
const
|
|
244
|
-
if (
|
|
426
|
+
const match = findCorpusMatch(entry, query);
|
|
427
|
+
if (!match) {
|
|
245
428
|
return;
|
|
246
429
|
}
|
|
247
430
|
|
|
248
|
-
|
|
431
|
+
fileMatchCount += 1;
|
|
432
|
+
this.fileMatches.set(entry.filePath, match);
|
|
433
|
+
const rankedEntry = {
|
|
434
|
+
filePath: entry.filePath,
|
|
435
|
+
recentRank: recentRanks.get(entry.filePath) ?? NO_RECENT_FILE_RANK,
|
|
436
|
+
score: match.score,
|
|
437
|
+
};
|
|
249
438
|
let inserted = false;
|
|
250
439
|
for (let index = 0; index < ranked.length; index += 1) {
|
|
251
440
|
const current = ranked[index];
|
|
252
|
-
|
|
441
|
+
const isBetter = rankedEntry.score > current.score
|
|
442
|
+
|| (
|
|
443
|
+
rankedEntry.score === current.score
|
|
444
|
+
&& (
|
|
445
|
+
rankedEntry.recentRank < current.recentRank
|
|
446
|
+
|| (
|
|
447
|
+
rankedEntry.recentRank === current.recentRank
|
|
448
|
+
&& entry.lowerPath < String(current.filePath).toLowerCase()
|
|
449
|
+
)
|
|
450
|
+
)
|
|
451
|
+
);
|
|
452
|
+
if (isBetter) {
|
|
253
453
|
ranked.splice(index, 0, rankedEntry);
|
|
254
454
|
inserted = true;
|
|
255
455
|
break;
|
|
@@ -266,45 +466,20 @@ export class QuickSwitcherController {
|
|
|
266
466
|
});
|
|
267
467
|
|
|
268
468
|
this.filteredFiles = ranked.map((entry) => entry.filePath);
|
|
469
|
+
this.fileMatchCount = fileMatchCount;
|
|
269
470
|
}
|
|
270
471
|
|
|
472
|
+
this.fileResultsTruncated = this.fileMatchCount > MAX_VISIBLE_RESULTS;
|
|
271
473
|
this.selectedIndex = 0;
|
|
272
474
|
this.renderResults(query);
|
|
273
475
|
}
|
|
274
476
|
|
|
275
|
-
fuzzyScore(entry, query) {
|
|
276
|
-
const name = entry.lowerDisplayName;
|
|
277
|
-
const nameOnly = entry.lowerFileName;
|
|
278
|
-
|
|
279
|
-
if (nameOnly.includes(query)) return 100 + (1 / nameOnly.length);
|
|
280
|
-
if (name.includes(query)) return 50 + (1 / name.length);
|
|
281
|
-
|
|
282
|
-
let queryIndex = 0;
|
|
283
|
-
let score = 0;
|
|
284
|
-
let consecutiveBonus = 0;
|
|
285
|
-
|
|
286
|
-
for (let i = 0; i < name.length && queryIndex < query.length; i++) {
|
|
287
|
-
if (name[i] === query[queryIndex]) {
|
|
288
|
-
queryIndex++;
|
|
289
|
-
consecutiveBonus += 1;
|
|
290
|
-
score += consecutiveBonus;
|
|
291
|
-
|
|
292
|
-
if (i === 0 || name[i - 1] === '/' || name[i - 1] === '-' || name[i - 1] === '_' || name[i - 1] === ' ') {
|
|
293
|
-
score += 5;
|
|
294
|
-
}
|
|
295
|
-
} else {
|
|
296
|
-
consecutiveBonus = 0;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return queryIndex === query.length ? score : 0;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
477
|
renderResults(query) {
|
|
304
478
|
if (!this.resultsList) return;
|
|
305
479
|
this.resultsList.innerHTML = '';
|
|
306
480
|
|
|
307
481
|
if (this.filteredFiles.length === 0) {
|
|
482
|
+
this.setActiveDescendant('');
|
|
308
483
|
if (this.hint) {
|
|
309
484
|
this.hint.textContent = query ? 'No files found' : 'No files in vault';
|
|
310
485
|
this.hint.classList.remove('hidden');
|
|
@@ -313,7 +488,13 @@ export class QuickSwitcherController {
|
|
|
313
488
|
}
|
|
314
489
|
|
|
315
490
|
if (this.hint) {
|
|
316
|
-
this.
|
|
491
|
+
const hint = this.fileResultsTruncated
|
|
492
|
+
? query
|
|
493
|
+
? `Showing the top ${MAX_VISIBLE_RESULTS} of ${this.fileMatchCount} matches. Refine the query to narrow results.`
|
|
494
|
+
: `Showing the first ${MAX_VISIBLE_RESULTS} of ${this.fileMatchCount} files.`
|
|
495
|
+
: '';
|
|
496
|
+
this.hint.textContent = hint;
|
|
497
|
+
this.hint.classList.toggle('hidden', !hint);
|
|
317
498
|
}
|
|
318
499
|
|
|
319
500
|
const fragment = document.createDocumentFragment();
|
|
@@ -325,15 +506,21 @@ export class QuickSwitcherController {
|
|
|
325
506
|
if (index === this.selectedIndex) {
|
|
326
507
|
item.classList.add('selected');
|
|
327
508
|
}
|
|
509
|
+
item.id = `${FILE_RESULT_ID_PREFIX}${index}`;
|
|
510
|
+
item.setAttribute('role', 'option');
|
|
511
|
+
item.setAttribute('aria-selected', index === this.selectedIndex ? 'true' : 'false');
|
|
328
512
|
item.dataset.index = String(index);
|
|
329
513
|
|
|
330
|
-
const fileName =
|
|
514
|
+
const fileName = getRawFileName(filePath);
|
|
331
515
|
const dirPath = getDirPath(filePath);
|
|
516
|
+
const match = this.fileMatches.get(filePath);
|
|
517
|
+
const corpusEntry = this.fileCorpus.find((entry) => entry.filePath === filePath);
|
|
518
|
+
const matchIndices = corpusEntry ? splitMatchIndices(corpusEntry, match?.indices) : { dirPath: [], fileName: [] };
|
|
332
519
|
|
|
333
520
|
item.innerHTML = `
|
|
334
|
-
|
|
335
|
-
<span class="qs-result-name">${
|
|
336
|
-
${dirPath ? `<span class="qs-result-path">${
|
|
521
|
+
${getVaultFileIconSvg(filePath, { className: 'qs-result-icon' })}
|
|
522
|
+
<span class="qs-result-name">${highlightText(fileName, matchIndices.fileName)}</span>
|
|
523
|
+
${dirPath ? `<span class="qs-result-path">${highlightText(dirPath, matchIndices.dirPath)}</span>` : ''}
|
|
337
524
|
`;
|
|
338
525
|
|
|
339
526
|
item.addEventListener('click', () => {
|
|
@@ -350,32 +537,7 @@ export class QuickSwitcherController {
|
|
|
350
537
|
});
|
|
351
538
|
|
|
352
539
|
this.resultsList.appendChild(fragment);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
highlightMatch(text, query) {
|
|
356
|
-
if (!query) return escapeHtml(text);
|
|
357
|
-
|
|
358
|
-
const lower = text.toLowerCase();
|
|
359
|
-
const idx = lower.indexOf(query);
|
|
360
|
-
|
|
361
|
-
if (idx >= 0) {
|
|
362
|
-
const before = text.slice(0, idx);
|
|
363
|
-
const match = text.slice(idx, idx + query.length);
|
|
364
|
-
const after = text.slice(idx + query.length);
|
|
365
|
-
return `${escapeHtml(before)}<mark>${escapeHtml(match)}</mark>${escapeHtml(after)}`;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
let result = '';
|
|
369
|
-
let queryIdx = 0;
|
|
370
|
-
for (let i = 0; i < text.length; i++) {
|
|
371
|
-
if (queryIdx < query.length && text[i].toLowerCase() === query[queryIdx]) {
|
|
372
|
-
result += `<mark>${escapeHtml(text[i])}</mark>`;
|
|
373
|
-
queryIdx++;
|
|
374
|
-
} else {
|
|
375
|
-
result += escapeHtml(text[i]);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return result;
|
|
540
|
+
this.updateSelection();
|
|
379
541
|
}
|
|
380
542
|
|
|
381
543
|
abortTextSearch({ invalidate = true } = {}) {
|
|
@@ -406,6 +568,8 @@ export class QuickSwitcherController {
|
|
|
406
568
|
renderTextState(message) {
|
|
407
569
|
if (!this.resultsList) return;
|
|
408
570
|
this.resultsList.innerHTML = '';
|
|
571
|
+
this.resultsList.setAttribute('aria-busy', message === 'Searching...' ? 'true' : 'false');
|
|
572
|
+
this.setActiveDescendant('');
|
|
409
573
|
if (this.hint) {
|
|
410
574
|
this.hint.textContent = message;
|
|
411
575
|
this.hint.classList.remove('hidden');
|
|
@@ -415,17 +579,20 @@ export class QuickSwitcherController {
|
|
|
415
579
|
renderTextResults(query = '') {
|
|
416
580
|
if (!this.resultsList) return;
|
|
417
581
|
this.resultsList.innerHTML = '';
|
|
582
|
+
this.resultsList.setAttribute('aria-busy', 'false');
|
|
418
583
|
|
|
419
584
|
if (!this.textResults?.files?.length || this.textResultItems.length === 0) {
|
|
585
|
+
this.setActiveDescendant('');
|
|
420
586
|
this.renderTextState(query ? 'No text matches found' : 'Type to search file text');
|
|
421
587
|
return;
|
|
422
588
|
}
|
|
423
589
|
|
|
424
590
|
if (this.hint) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
? 'Showing the first matches. Refine the query to narrow results.'
|
|
591
|
+
const hint = this.textResults.truncated
|
|
592
|
+
? `Showing partial results: ${this.textResults.files.length} files and ${formatMatchCount(this.textResults.matchCount, { truncated: true })}. Refine the query to narrow results.`
|
|
428
593
|
: '';
|
|
594
|
+
this.hint.textContent = hint;
|
|
595
|
+
this.hint.classList.toggle('hidden', !hint);
|
|
429
596
|
}
|
|
430
597
|
|
|
431
598
|
const fragment = document.createDocumentFragment();
|
|
@@ -434,13 +601,15 @@ export class QuickSwitcherController {
|
|
|
434
601
|
this.textResults.files.forEach((fileGroup) => {
|
|
435
602
|
const group = document.createElement('section');
|
|
436
603
|
group.className = 'qs-text-group';
|
|
604
|
+
group.setAttribute('role', 'group');
|
|
605
|
+
group.setAttribute('aria-label', fileGroup.file);
|
|
437
606
|
|
|
438
607
|
const header = document.createElement('div');
|
|
439
608
|
header.className = 'qs-text-group-header';
|
|
440
609
|
header.innerHTML = `
|
|
441
610
|
<span class="qs-text-file-name">${escapeHtml(getFileName(fileGroup.file))}</span>
|
|
442
611
|
<span class="qs-text-file-meta">${escapeHtml(getDirPath(fileGroup.file))}</span>
|
|
443
|
-
<span class="qs-text-count">${escapeHtml(formatMatchCount(fileGroup.matchCount))}</span>
|
|
612
|
+
<span class="qs-text-count">${escapeHtml(formatMatchCount(fileGroup.matchCount, { truncated: fileGroup.truncated }))}</span>
|
|
444
613
|
`;
|
|
445
614
|
group.appendChild(header);
|
|
446
615
|
|
|
@@ -452,6 +621,9 @@ export class QuickSwitcherController {
|
|
|
452
621
|
if (itemIndex === this.selectedTextIndex) {
|
|
453
622
|
item.classList.add('selected');
|
|
454
623
|
}
|
|
624
|
+
item.id = `${TEXT_RESULT_ID_PREFIX}${itemIndex}`;
|
|
625
|
+
item.setAttribute('role', 'option');
|
|
626
|
+
item.setAttribute('aria-selected', itemIndex === this.selectedTextIndex ? 'true' : 'false');
|
|
455
627
|
item.dataset.textIndex = String(itemIndex);
|
|
456
628
|
item.innerHTML = `
|
|
457
629
|
<span class="qs-text-line">L${escapeHtml(String(snippet.line ?? 1))}</span>
|
|
@@ -473,6 +645,7 @@ export class QuickSwitcherController {
|
|
|
473
645
|
});
|
|
474
646
|
|
|
475
647
|
this.resultsList.appendChild(fragment);
|
|
648
|
+
this.updateSelection();
|
|
476
649
|
}
|
|
477
650
|
|
|
478
651
|
highlightSnippet(snippet = {}) {
|
|
@@ -501,25 +674,35 @@ export class QuickSwitcherController {
|
|
|
501
674
|
if (this.mode === 'text') {
|
|
502
675
|
const items = this.resultsList.querySelectorAll('.qs-text-item');
|
|
503
676
|
items.forEach((item, i) => {
|
|
504
|
-
|
|
677
|
+
const isSelected = i === this.selectedTextIndex;
|
|
678
|
+
item.classList.toggle('selected', isSelected);
|
|
679
|
+
item.setAttribute('aria-selected', isSelected ? 'true' : 'false');
|
|
505
680
|
});
|
|
506
681
|
items[this.selectedTextIndex]?.scrollIntoView({ block: 'nearest' });
|
|
682
|
+
this.setActiveDescendant(items[this.selectedTextIndex]?.id ?? '');
|
|
507
683
|
return;
|
|
508
684
|
}
|
|
509
685
|
|
|
510
686
|
const items = this.resultsList.querySelectorAll('.qs-result-item');
|
|
511
687
|
items.forEach((item, i) => {
|
|
512
|
-
|
|
688
|
+
const isSelected = i === this.selectedIndex;
|
|
689
|
+
item.classList.toggle('selected', isSelected);
|
|
690
|
+
item.setAttribute('aria-selected', isSelected ? 'true' : 'false');
|
|
513
691
|
});
|
|
514
692
|
|
|
515
693
|
items[this.selectedIndex]?.scrollIntoView({ block: 'nearest' });
|
|
694
|
+
this.setActiveDescendant(items[this.selectedIndex]?.id ?? '');
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
setActiveDescendant(id = '') {
|
|
698
|
+
this.input?.setAttribute('aria-activedescendant', id);
|
|
516
699
|
}
|
|
517
700
|
|
|
518
701
|
confirmSelection() {
|
|
519
702
|
if (this.mode === 'text') {
|
|
520
703
|
const match = this.textResultItems[this.selectedTextIndex];
|
|
521
704
|
if (match) {
|
|
522
|
-
this.close();
|
|
705
|
+
this.close({ restoreFocus: false });
|
|
523
706
|
this.onTextMatchSelect?.(match);
|
|
524
707
|
}
|
|
525
708
|
return;
|
|
@@ -527,7 +710,7 @@ export class QuickSwitcherController {
|
|
|
527
710
|
|
|
528
711
|
const filePath = this.filteredFiles[this.selectedIndex];
|
|
529
712
|
if (filePath) {
|
|
530
|
-
this.close();
|
|
713
|
+
this.close({ restoreFocus: false });
|
|
531
714
|
this.onFileSelect?.(filePath);
|
|
532
715
|
}
|
|
533
716
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export const DEFAULT_SEARCH_DEBOUNCE_MS =
|
|
1
|
+
export const DEFAULT_SEARCH_DEBOUNCE_MS = 300;
|
|
2
2
|
const TEXT_RESULT_LIMIT = 50;
|
|
3
3
|
|
|
4
|
-
export function formatMatchCount(count = 0) {
|
|
4
|
+
export function formatMatchCount(count = 0, { truncated = false } = {}) {
|
|
5
5
|
const normalized = Number(count) || 0;
|
|
6
|
-
return `${normalized} ${normalized === 1 ? 'match' : 'matches'}`;
|
|
6
|
+
return `${normalized}${truncated ? '+' : ''} ${normalized === 1 ? 'match' : 'matches'}`;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function flattenTextResults(payload = {}) {
|
|
@@ -16,6 +16,9 @@ export class ToastController {
|
|
|
16
16
|
: 0;
|
|
17
17
|
const toast = document.createElement('div');
|
|
18
18
|
toast.className = 'toast';
|
|
19
|
+
if (typeof normalizedOptions.tone === 'string' && normalizedOptions.tone) {
|
|
20
|
+
toast.dataset.tone = normalizedOptions.tone;
|
|
21
|
+
}
|
|
19
22
|
const content = document.createElement('span');
|
|
20
23
|
content.className = 'toast__message';
|
|
21
24
|
content.textContent = message;
|