collabmd 0.1.44 → 0.1.45
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/dist/client/assets/{chunk-AXR3VHVW-0GTk_evH.js → chunk-ERSWUTIN-ME9RVNel.js} +3 -3
- package/dist/client/assets/{chunk-CZQZEIFA-DT3N9fo7.js → chunk-YGJVY4SA-CjSe7Bx7.js} +2 -2
- package/dist/client/assets/{comment-threads-D-iKphIp.js → comment-threads-DDegmaFO.js} +1 -1
- package/dist/client/assets/drawio-editor-ClE8jpoG.js +9 -0
- package/dist/client/assets/drawioEditor-CXaTM9Ds.js +2 -0
- package/dist/client/assets/editor-session-GTwZS6Df.js +16 -0
- package/dist/client/assets/embedded-editor-base-CiNEc8vy.css +1 -0
- package/dist/client/assets/{en-GADW4C6J-DQ6zyCEM.js → en-7NFCYGKX-BrgYZAEe.js} +1 -1
- package/dist/client/assets/{excalidraw-editor-BHu6k8d4.js → excalidraw-editor-BZVM81BO.js} +2 -2
- package/dist/client/assets/excalidraw-editor-CyGwOhLz.css +1 -0
- package/dist/client/assets/excalidraw-scene-C8n9CHsK.js +176 -0
- package/dist/client/assets/excalidrawEditor-DxJanCze.js +2 -0
- package/dist/client/assets/export-page-CUfws2T-.js +39 -0
- package/dist/client/assets/exportDocument-BCuxqXDY.js +2 -0
- package/dist/client/assets/exportDocument-DV-OX74E.css +1 -0
- package/dist/client/assets/image-QXRYHUNP-lfTHE3bb.js +1 -0
- package/dist/client/assets/index-C3YnUgMD.css +1 -0
- package/dist/client/assets/index-D2wXraJ1.js +2 -0
- package/dist/client/assets/main-DtiGz7uI.js +795 -0
- package/dist/client/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
- package/dist/client/assets/preview-diagram-utils-txBCoW8d.js +1 -0
- package/dist/client/assets/quick-switcher-controller-shW_6vPY.js +12 -0
- package/dist/client/assets/room-CQLoqtMo.js +1 -0
- package/dist/client/assets/runtime-config-DSGmGE9V.js +1 -0
- package/dist/client/assets/vault-api-client-C6bcGwy-.js +5 -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 +97 -27
- package/package.json +10 -12
- 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 +94 -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 +1 -1
- package/src/client/application/app-shell/git-feature.js +4 -9
- package/src/client/application/app-shell/presence-feature.js +16 -10
- package/src/client/application/app-shell/ui-feature-shell.js +33 -15
- 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/workspace-feature.js +22 -83
- package/src/client/application/app-shell-elements.js +7 -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/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/workspace-preview-controller.js +0 -3
- package/src/client/bootstrap/collabmd-app-shell.js +125 -39
- package/src/client/domain/excalidraw-collaboration.js +0 -2
- package/src/client/domain/room.js +38 -0
- 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 +36 -9
- package/src/client/infrastructure/editor-view-adapter.js +87 -3
- package/src/client/infrastructure/lobby-presence.js +1 -0
- package/src/client/infrastructure/vault-api-client.js +25 -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 +65 -14
- package/src/client/presentation/comment-ui-controller.js +5 -1
- package/src/client/presentation/file-action-controller.js +32 -0
- package/src/client/presentation/file-explorer-controller.js +33 -3
- package/src/client/presentation/file-explorer-view.js +49 -5
- package/src/client/presentation/file-tree-state.js +10 -4
- package/src/client/presentation/quick-switcher-controller.js +243 -72
- package/src/client/presentation/quick-switcher-text-search.js +2 -2
- package/src/client/presentation/toast-controller.js +3 -0
- package/src/client/styles/components/feedback.css +6 -0
- 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 +5 -18
- 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/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 +25 -6
- package/src/client/styles/layout/sidebar.css +47 -15
- package/src/client/styles/overrides/codemirror.css +217 -15
- package/src/server/application/workspace-reconciliation.js +1 -1
- package/src/server/create-app-server.js +2 -3
- package/src/server/domain/bases/base-query-service.js +1 -77
- package/src/server/domain/comment-overview.js +10 -3
- package/src/server/domain/docx-exporter.js +24 -33
- package/src/server/domain/ripgrep-search-service.js +3 -1
- package/src/server/domain/workspace-state.js +32 -11
- package/src/server/infrastructure/git/diff-service.js +24 -13
- package/src/server/infrastructure/git/git-service.js +40 -10
- package/src/server/infrastructure/git/history-service.js +42 -29
- package/src/server/infrastructure/git/path-utils.js +1 -1
- package/src/server/infrastructure/git/responses.js +0 -95
- package/src/server/infrastructure/github/github-app-client.js +22 -15
- package/src/server/infrastructure/http/create-request-handler.js +2 -8
- package/src/server/infrastructure/http/create-vault-api-command-handler.js +5 -5
- package/src/server/infrastructure/http/http-errors.js +7 -1
- package/dist/client/assets/bases-preview-controller-2pLHk0uk.js +0 -143
- package/dist/client/assets/button-BCEiw4Rw.js +0 -1
- package/dist/client/assets/diagram-action-icons-aa_KOv9h.js +0 -36
- 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-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/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/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/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
|
@@ -7,7 +7,14 @@ import {
|
|
|
7
7
|
formatMatchCount,
|
|
8
8
|
} from './quick-switcher-text-search.js';
|
|
9
9
|
|
|
10
|
-
const MAX_VISIBLE_RESULTS =
|
|
10
|
+
const MAX_VISIBLE_RESULTS = 30;
|
|
11
|
+
const NO_RECENT_FILE_RANK = Number.MAX_SAFE_INTEGER;
|
|
12
|
+
const FILE_RESULT_ID_PREFIX = 'quick-switcher-file-';
|
|
13
|
+
const TEXT_RESULT_ID_PREFIX = 'quick-switcher-text-';
|
|
14
|
+
|
|
15
|
+
function getRawFileName(filePath) {
|
|
16
|
+
return String(filePath ?? '').split('/').pop() || String(filePath ?? '');
|
|
17
|
+
}
|
|
11
18
|
|
|
12
19
|
function createCorpusEntry(filePath) {
|
|
13
20
|
const displayName = stripVaultFileExtension(filePath);
|
|
@@ -33,9 +40,116 @@ function getDirPath(filePath) {
|
|
|
33
40
|
return displayName.includes('/') ? displayName.substring(0, displayName.lastIndexOf('/')) : '';
|
|
34
41
|
}
|
|
35
42
|
|
|
43
|
+
function createRangeIndices(start, length) {
|
|
44
|
+
return Array.from({ length }, (_, index) => start + index);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function findFuzzyMatch(text, query) {
|
|
48
|
+
const indices = [];
|
|
49
|
+
let queryIndex = 0;
|
|
50
|
+
let score = 0;
|
|
51
|
+
let consecutiveBonus = 0;
|
|
52
|
+
|
|
53
|
+
for (let index = 0; index < text.length && queryIndex < query.length; index += 1) {
|
|
54
|
+
if (text[index] !== query[queryIndex]) {
|
|
55
|
+
consecutiveBonus = 0;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
queryIndex += 1;
|
|
60
|
+
indices.push(index);
|
|
61
|
+
consecutiveBonus += 1;
|
|
62
|
+
score += consecutiveBonus;
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
index === 0
|
|
66
|
+
|| text[index - 1] === '/'
|
|
67
|
+
|| text[index - 1] === '-'
|
|
68
|
+
|| text[index - 1] === '_'
|
|
69
|
+
|| text[index - 1] === ' '
|
|
70
|
+
) {
|
|
71
|
+
score += 5;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return queryIndex === query.length && score > 0
|
|
76
|
+
? { indices, score }
|
|
77
|
+
: null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function findCorpusMatch(entry, query) {
|
|
81
|
+
const fileIndex = entry.lowerFileName.indexOf(query);
|
|
82
|
+
if (fileIndex >= 0) {
|
|
83
|
+
return {
|
|
84
|
+
indices: createRangeIndices(fileIndex + (entry.displayName.length - entry.fileName.length), query.length),
|
|
85
|
+
score: 100 + (1 / entry.fileName.length),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const pathIndex = entry.lowerDisplayName.indexOf(query);
|
|
90
|
+
if (pathIndex >= 0) {
|
|
91
|
+
return {
|
|
92
|
+
indices: createRangeIndices(pathIndex, query.length),
|
|
93
|
+
score: 50 + (1 / entry.displayName.length),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return findFuzzyMatch(entry.lowerDisplayName, query);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function splitMatchIndices(entry, indices = []) {
|
|
101
|
+
const fileNameStart = entry.displayName.length - entry.fileName.length;
|
|
102
|
+
return {
|
|
103
|
+
dirPath: indices.filter((index) => index < fileNameStart),
|
|
104
|
+
fileName: indices
|
|
105
|
+
.filter((index) => index >= fileNameStart)
|
|
106
|
+
.map((index) => index - fileNameStart),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function highlightText(text, indices = []) {
|
|
111
|
+
if (indices.length === 0) {
|
|
112
|
+
return escapeHtml(text);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const matchedIndices = new Set(indices);
|
|
116
|
+
let result = '';
|
|
117
|
+
let runStart = null;
|
|
118
|
+
let cursor = 0;
|
|
119
|
+
|
|
120
|
+
const appendRun = (runEnd) => {
|
|
121
|
+
result += escapeHtml(text.slice(cursor, runStart));
|
|
122
|
+
result += `<mark>${escapeHtml(text.slice(runStart, runEnd + 1))}</mark>`;
|
|
123
|
+
cursor = runEnd + 1;
|
|
124
|
+
runStart = null;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
128
|
+
if (!matchedIndices.has(index)) {
|
|
129
|
+
if (runStart !== null) {
|
|
130
|
+
appendRun(index - 1);
|
|
131
|
+
}
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (runStart === null) {
|
|
136
|
+
runStart = index;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (runStart !== null) {
|
|
141
|
+
appendRun(text.length - 1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
result += escapeHtml(text.slice(cursor));
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
36
148
|
export class QuickSwitcherController {
|
|
37
149
|
constructor({
|
|
38
150
|
getFileList,
|
|
151
|
+
getFileMetadata = () => [],
|
|
152
|
+
getRecentFiles = () => [],
|
|
39
153
|
getSearchConfig = () => ({}),
|
|
40
154
|
onFileSelect,
|
|
41
155
|
onTextMatchSelect = null,
|
|
@@ -43,6 +157,8 @@ export class QuickSwitcherController {
|
|
|
43
157
|
searchText = null,
|
|
44
158
|
}) {
|
|
45
159
|
this.getFileList = getFileList;
|
|
160
|
+
this.getFileMetadata = getFileMetadata;
|
|
161
|
+
this.getRecentFiles = getRecentFiles;
|
|
46
162
|
this.getSearchConfig = getSearchConfig;
|
|
47
163
|
this.onFileSelect = onFileSelect;
|
|
48
164
|
this.onTextMatchSelect = onTextMatchSelect;
|
|
@@ -53,14 +169,20 @@ export class QuickSwitcherController {
|
|
|
53
169
|
this.input = document.getElementById('quickSwitcherInput');
|
|
54
170
|
this.resultsList = document.getElementById('quickSwitcherResults');
|
|
55
171
|
this.hint = document.getElementById('quickSwitcherHint');
|
|
172
|
+
this.scope = document.getElementById('quickSwitcherScope');
|
|
56
173
|
this.modeTabs = Array.from(document.querySelectorAll?.('[data-qs-mode]') ?? []);
|
|
57
174
|
|
|
58
175
|
this.filteredFiles = [];
|
|
176
|
+
this.fileMatches = new Map();
|
|
177
|
+
this.fileNameCounts = new Map();
|
|
178
|
+
this.fileMatchCount = 0;
|
|
179
|
+
this.fileResultsTruncated = false;
|
|
59
180
|
this.fileCorpus = [];
|
|
60
181
|
this.lastFileListRef = null;
|
|
61
182
|
this.selectedIndex = 0;
|
|
62
183
|
this.selectedTextIndex = 0;
|
|
63
184
|
this.isOpen = false;
|
|
185
|
+
this.previousActiveElement = null;
|
|
64
186
|
this.mode = 'files';
|
|
65
187
|
this.textResults = null;
|
|
66
188
|
this.textResultItems = [];
|
|
@@ -119,11 +241,13 @@ export class QuickSwitcherController {
|
|
|
119
241
|
open() {
|
|
120
242
|
if (!this.overlay) return;
|
|
121
243
|
|
|
244
|
+
this.previousActiveElement = document.activeElement;
|
|
122
245
|
this.isOpen = true;
|
|
123
246
|
this.input.value = '';
|
|
124
247
|
this.selectedIndex = 0;
|
|
125
248
|
this.selectedTextIndex = 0;
|
|
126
249
|
this.overlay.classList.add('visible');
|
|
250
|
+
this.overlay.setAttribute('aria-hidden', 'false');
|
|
127
251
|
this.setMode('files', { preserveInput: true });
|
|
128
252
|
|
|
129
253
|
// The overlay transitions visibility hidden→visible over 120ms.
|
|
@@ -171,15 +295,23 @@ export class QuickSwitcherController {
|
|
|
171
295
|
}
|
|
172
296
|
}
|
|
173
297
|
|
|
174
|
-
close() {
|
|
298
|
+
close({ restoreFocus = true } = {}) {
|
|
175
299
|
if (!this.overlay) return;
|
|
176
300
|
|
|
301
|
+
const previousActiveElement = this.previousActiveElement;
|
|
177
302
|
this._cancelPendingFocus();
|
|
178
303
|
this.abortTextSearch();
|
|
179
304
|
this.isOpen = false;
|
|
180
305
|
this.overlay.classList.remove('visible');
|
|
306
|
+
this.overlay.setAttribute('aria-hidden', 'true');
|
|
181
307
|
this.input.value = '';
|
|
182
308
|
this.resultsList.innerHTML = '';
|
|
309
|
+
this.setActiveDescendant('');
|
|
310
|
+
this.previousActiveElement = null;
|
|
311
|
+
|
|
312
|
+
if (restoreFocus) {
|
|
313
|
+
previousActiveElement?.focus?.();
|
|
314
|
+
}
|
|
183
315
|
}
|
|
184
316
|
|
|
185
317
|
toggle() {
|
|
@@ -206,10 +338,17 @@ export class QuickSwitcherController {
|
|
|
206
338
|
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
207
339
|
});
|
|
208
340
|
|
|
341
|
+
this.scope?.classList.toggle('hidden', normalizedMode !== 'text');
|
|
342
|
+
this.scope?.setAttribute('aria-hidden', normalizedMode === 'text' ? 'false' : 'true');
|
|
343
|
+
|
|
209
344
|
if (this.input) {
|
|
210
345
|
this.input.placeholder = normalizedMode === 'text'
|
|
211
346
|
? 'Search text in files...'
|
|
212
347
|
: 'Search files...';
|
|
348
|
+
this.input.setAttribute(
|
|
349
|
+
'aria-label',
|
|
350
|
+
normalizedMode === 'text' ? 'Search text in files' : 'Search files',
|
|
351
|
+
);
|
|
213
352
|
}
|
|
214
353
|
|
|
215
354
|
this.handleInput();
|
|
@@ -231,25 +370,70 @@ export class QuickSwitcherController {
|
|
|
231
370
|
if (allFiles !== this.lastFileListRef) {
|
|
232
371
|
this.lastFileListRef = allFiles;
|
|
233
372
|
this.fileCorpus = allFiles.map((filePath) => createCorpusEntry(filePath));
|
|
373
|
+
this.fileNameCounts = new Map();
|
|
374
|
+
this.fileCorpus.forEach((entry) => {
|
|
375
|
+
const count = this.fileNameCounts.get(entry.lowerFileName) ?? 0;
|
|
376
|
+
this.fileNameCounts.set(entry.lowerFileName, count + 1);
|
|
377
|
+
});
|
|
234
378
|
}
|
|
235
379
|
|
|
380
|
+
this.fileMatches.clear();
|
|
381
|
+
const fileMetadata = this.getFileMetadata?.() ?? [];
|
|
382
|
+
const modifiedTimes = new Map(
|
|
383
|
+
(Array.isArray(fileMetadata) ? fileMetadata : []).map((entry) => [
|
|
384
|
+
entry?.path,
|
|
385
|
+
Number.isFinite(Number(entry?.mtimeMs)) ? Number(entry.mtimeMs) : 0,
|
|
386
|
+
]),
|
|
387
|
+
);
|
|
388
|
+
const recentFiles = this.getRecentFiles?.() ?? [];
|
|
389
|
+
const recentRanks = new Map(
|
|
390
|
+
(Array.isArray(recentFiles) ? recentFiles : []).map((filePath, index) => [filePath, index]),
|
|
391
|
+
);
|
|
236
392
|
if (!query) {
|
|
237
|
-
this.filteredFiles = this.fileCorpus
|
|
393
|
+
this.filteredFiles = [...this.fileCorpus]
|
|
394
|
+
.sort((left, right) => {
|
|
395
|
+
const modifiedDelta = (modifiedTimes.get(right.filePath) ?? 0) - (modifiedTimes.get(left.filePath) ?? 0);
|
|
396
|
+
if (modifiedDelta !== 0) {
|
|
397
|
+
return modifiedDelta;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return (recentRanks.get(left.filePath) ?? NO_RECENT_FILE_RANK)
|
|
401
|
+
- (recentRanks.get(right.filePath) ?? NO_RECENT_FILE_RANK);
|
|
402
|
+
})
|
|
238
403
|
.slice(0, MAX_VISIBLE_RESULTS)
|
|
239
404
|
.map((entry) => entry.filePath);
|
|
405
|
+
this.fileMatchCount = this.fileCorpus.length;
|
|
240
406
|
} else {
|
|
241
407
|
const ranked = [];
|
|
408
|
+
let fileMatchCount = 0;
|
|
242
409
|
this.fileCorpus.forEach((entry) => {
|
|
243
|
-
const
|
|
244
|
-
if (
|
|
410
|
+
const match = findCorpusMatch(entry, query);
|
|
411
|
+
if (!match) {
|
|
245
412
|
return;
|
|
246
413
|
}
|
|
247
414
|
|
|
248
|
-
|
|
415
|
+
fileMatchCount += 1;
|
|
416
|
+
this.fileMatches.set(entry.filePath, match);
|
|
417
|
+
const rankedEntry = {
|
|
418
|
+
filePath: entry.filePath,
|
|
419
|
+
recentRank: recentRanks.get(entry.filePath) ?? NO_RECENT_FILE_RANK,
|
|
420
|
+
score: match.score,
|
|
421
|
+
};
|
|
249
422
|
let inserted = false;
|
|
250
423
|
for (let index = 0; index < ranked.length; index += 1) {
|
|
251
424
|
const current = ranked[index];
|
|
252
|
-
|
|
425
|
+
const isBetter = rankedEntry.score > current.score
|
|
426
|
+
|| (
|
|
427
|
+
rankedEntry.score === current.score
|
|
428
|
+
&& (
|
|
429
|
+
rankedEntry.recentRank < current.recentRank
|
|
430
|
+
|| (
|
|
431
|
+
rankedEntry.recentRank === current.recentRank
|
|
432
|
+
&& entry.lowerPath < String(current.filePath).toLowerCase()
|
|
433
|
+
)
|
|
434
|
+
)
|
|
435
|
+
);
|
|
436
|
+
if (isBetter) {
|
|
253
437
|
ranked.splice(index, 0, rankedEntry);
|
|
254
438
|
inserted = true;
|
|
255
439
|
break;
|
|
@@ -266,45 +450,20 @@ export class QuickSwitcherController {
|
|
|
266
450
|
});
|
|
267
451
|
|
|
268
452
|
this.filteredFiles = ranked.map((entry) => entry.filePath);
|
|
453
|
+
this.fileMatchCount = fileMatchCount;
|
|
269
454
|
}
|
|
270
455
|
|
|
456
|
+
this.fileResultsTruncated = this.fileMatchCount > MAX_VISIBLE_RESULTS;
|
|
271
457
|
this.selectedIndex = 0;
|
|
272
458
|
this.renderResults(query);
|
|
273
459
|
}
|
|
274
460
|
|
|
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
461
|
renderResults(query) {
|
|
304
462
|
if (!this.resultsList) return;
|
|
305
463
|
this.resultsList.innerHTML = '';
|
|
306
464
|
|
|
307
465
|
if (this.filteredFiles.length === 0) {
|
|
466
|
+
this.setActiveDescendant('');
|
|
308
467
|
if (this.hint) {
|
|
309
468
|
this.hint.textContent = query ? 'No files found' : 'No files in vault';
|
|
310
469
|
this.hint.classList.remove('hidden');
|
|
@@ -313,7 +472,13 @@ export class QuickSwitcherController {
|
|
|
313
472
|
}
|
|
314
473
|
|
|
315
474
|
if (this.hint) {
|
|
316
|
-
this.
|
|
475
|
+
const hint = this.fileResultsTruncated
|
|
476
|
+
? query
|
|
477
|
+
? `Showing the top ${MAX_VISIBLE_RESULTS} of ${this.fileMatchCount} matches. Refine the query to narrow results.`
|
|
478
|
+
: `Showing the first ${MAX_VISIBLE_RESULTS} of ${this.fileMatchCount} files.`
|
|
479
|
+
: '';
|
|
480
|
+
this.hint.textContent = hint;
|
|
481
|
+
this.hint.classList.toggle('hidden', !hint);
|
|
317
482
|
}
|
|
318
483
|
|
|
319
484
|
const fragment = document.createDocumentFragment();
|
|
@@ -325,15 +490,25 @@ export class QuickSwitcherController {
|
|
|
325
490
|
if (index === this.selectedIndex) {
|
|
326
491
|
item.classList.add('selected');
|
|
327
492
|
}
|
|
493
|
+
item.id = `${FILE_RESULT_ID_PREFIX}${index}`;
|
|
494
|
+
item.setAttribute('role', 'option');
|
|
495
|
+
item.setAttribute('aria-selected', index === this.selectedIndex ? 'true' : 'false');
|
|
328
496
|
item.dataset.index = String(index);
|
|
329
497
|
|
|
330
498
|
const fileName = getFileName(filePath);
|
|
499
|
+
const rawFileName = getRawFileName(filePath);
|
|
331
500
|
const dirPath = getDirPath(filePath);
|
|
501
|
+
const match = this.fileMatches.get(filePath);
|
|
502
|
+
const corpusEntry = this.fileCorpus.find((entry) => entry.filePath === filePath);
|
|
503
|
+
const matchIndices = corpusEntry ? splitMatchIndices(corpusEntry, match?.indices) : { dirPath: [], fileName: [] };
|
|
504
|
+
const displayFileName = (this.fileNameCounts.get(fileName.toLowerCase()) ?? 0) > 1
|
|
505
|
+
? rawFileName
|
|
506
|
+
: fileName;
|
|
332
507
|
|
|
333
508
|
item.innerHTML = `
|
|
334
509
|
<svg class="qs-result-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
335
|
-
<span class="qs-result-name">${
|
|
336
|
-
${dirPath ? `<span class="qs-result-path">${
|
|
510
|
+
<span class="qs-result-name">${highlightText(displayFileName, matchIndices.fileName)}</span>
|
|
511
|
+
${dirPath ? `<span class="qs-result-path">${highlightText(dirPath, matchIndices.dirPath)}</span>` : ''}
|
|
337
512
|
`;
|
|
338
513
|
|
|
339
514
|
item.addEventListener('click', () => {
|
|
@@ -350,32 +525,7 @@ export class QuickSwitcherController {
|
|
|
350
525
|
});
|
|
351
526
|
|
|
352
527
|
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;
|
|
528
|
+
this.updateSelection();
|
|
379
529
|
}
|
|
380
530
|
|
|
381
531
|
abortTextSearch({ invalidate = true } = {}) {
|
|
@@ -406,6 +556,8 @@ export class QuickSwitcherController {
|
|
|
406
556
|
renderTextState(message) {
|
|
407
557
|
if (!this.resultsList) return;
|
|
408
558
|
this.resultsList.innerHTML = '';
|
|
559
|
+
this.resultsList.setAttribute('aria-busy', message === 'Searching...' ? 'true' : 'false');
|
|
560
|
+
this.setActiveDescendant('');
|
|
409
561
|
if (this.hint) {
|
|
410
562
|
this.hint.textContent = message;
|
|
411
563
|
this.hint.classList.remove('hidden');
|
|
@@ -415,17 +567,20 @@ export class QuickSwitcherController {
|
|
|
415
567
|
renderTextResults(query = '') {
|
|
416
568
|
if (!this.resultsList) return;
|
|
417
569
|
this.resultsList.innerHTML = '';
|
|
570
|
+
this.resultsList.setAttribute('aria-busy', 'false');
|
|
418
571
|
|
|
419
572
|
if (!this.textResults?.files?.length || this.textResultItems.length === 0) {
|
|
573
|
+
this.setActiveDescendant('');
|
|
420
574
|
this.renderTextState(query ? 'No text matches found' : 'Type to search file text');
|
|
421
575
|
return;
|
|
422
576
|
}
|
|
423
577
|
|
|
424
578
|
if (this.hint) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
? 'Showing the first matches. Refine the query to narrow results.'
|
|
579
|
+
const hint = this.textResults.truncated
|
|
580
|
+
? `Showing partial results: ${this.textResults.files.length} files and ${formatMatchCount(this.textResults.matchCount, { truncated: true })}. Refine the query to narrow results.`
|
|
428
581
|
: '';
|
|
582
|
+
this.hint.textContent = hint;
|
|
583
|
+
this.hint.classList.toggle('hidden', !hint);
|
|
429
584
|
}
|
|
430
585
|
|
|
431
586
|
const fragment = document.createDocumentFragment();
|
|
@@ -434,13 +589,15 @@ export class QuickSwitcherController {
|
|
|
434
589
|
this.textResults.files.forEach((fileGroup) => {
|
|
435
590
|
const group = document.createElement('section');
|
|
436
591
|
group.className = 'qs-text-group';
|
|
592
|
+
group.setAttribute('role', 'group');
|
|
593
|
+
group.setAttribute('aria-label', fileGroup.file);
|
|
437
594
|
|
|
438
595
|
const header = document.createElement('div');
|
|
439
596
|
header.className = 'qs-text-group-header';
|
|
440
597
|
header.innerHTML = `
|
|
441
598
|
<span class="qs-text-file-name">${escapeHtml(getFileName(fileGroup.file))}</span>
|
|
442
599
|
<span class="qs-text-file-meta">${escapeHtml(getDirPath(fileGroup.file))}</span>
|
|
443
|
-
<span class="qs-text-count">${escapeHtml(formatMatchCount(fileGroup.matchCount))}</span>
|
|
600
|
+
<span class="qs-text-count">${escapeHtml(formatMatchCount(fileGroup.matchCount, { truncated: fileGroup.truncated }))}</span>
|
|
444
601
|
`;
|
|
445
602
|
group.appendChild(header);
|
|
446
603
|
|
|
@@ -452,6 +609,9 @@ export class QuickSwitcherController {
|
|
|
452
609
|
if (itemIndex === this.selectedTextIndex) {
|
|
453
610
|
item.classList.add('selected');
|
|
454
611
|
}
|
|
612
|
+
item.id = `${TEXT_RESULT_ID_PREFIX}${itemIndex}`;
|
|
613
|
+
item.setAttribute('role', 'option');
|
|
614
|
+
item.setAttribute('aria-selected', itemIndex === this.selectedTextIndex ? 'true' : 'false');
|
|
455
615
|
item.dataset.textIndex = String(itemIndex);
|
|
456
616
|
item.innerHTML = `
|
|
457
617
|
<span class="qs-text-line">L${escapeHtml(String(snippet.line ?? 1))}</span>
|
|
@@ -473,6 +633,7 @@ export class QuickSwitcherController {
|
|
|
473
633
|
});
|
|
474
634
|
|
|
475
635
|
this.resultsList.appendChild(fragment);
|
|
636
|
+
this.updateSelection();
|
|
476
637
|
}
|
|
477
638
|
|
|
478
639
|
highlightSnippet(snippet = {}) {
|
|
@@ -501,25 +662,35 @@ export class QuickSwitcherController {
|
|
|
501
662
|
if (this.mode === 'text') {
|
|
502
663
|
const items = this.resultsList.querySelectorAll('.qs-text-item');
|
|
503
664
|
items.forEach((item, i) => {
|
|
504
|
-
|
|
665
|
+
const isSelected = i === this.selectedTextIndex;
|
|
666
|
+
item.classList.toggle('selected', isSelected);
|
|
667
|
+
item.setAttribute('aria-selected', isSelected ? 'true' : 'false');
|
|
505
668
|
});
|
|
506
669
|
items[this.selectedTextIndex]?.scrollIntoView({ block: 'nearest' });
|
|
670
|
+
this.setActiveDescendant(items[this.selectedTextIndex]?.id ?? '');
|
|
507
671
|
return;
|
|
508
672
|
}
|
|
509
673
|
|
|
510
674
|
const items = this.resultsList.querySelectorAll('.qs-result-item');
|
|
511
675
|
items.forEach((item, i) => {
|
|
512
|
-
|
|
676
|
+
const isSelected = i === this.selectedIndex;
|
|
677
|
+
item.classList.toggle('selected', isSelected);
|
|
678
|
+
item.setAttribute('aria-selected', isSelected ? 'true' : 'false');
|
|
513
679
|
});
|
|
514
680
|
|
|
515
681
|
items[this.selectedIndex]?.scrollIntoView({ block: 'nearest' });
|
|
682
|
+
this.setActiveDescendant(items[this.selectedIndex]?.id ?? '');
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
setActiveDescendant(id = '') {
|
|
686
|
+
this.input?.setAttribute('aria-activedescendant', id);
|
|
516
687
|
}
|
|
517
688
|
|
|
518
689
|
confirmSelection() {
|
|
519
690
|
if (this.mode === 'text') {
|
|
520
691
|
const match = this.textResultItems[this.selectedTextIndex];
|
|
521
692
|
if (match) {
|
|
522
|
-
this.close();
|
|
693
|
+
this.close({ restoreFocus: false });
|
|
523
694
|
this.onTextMatchSelect?.(match);
|
|
524
695
|
}
|
|
525
696
|
return;
|
|
@@ -527,7 +698,7 @@ export class QuickSwitcherController {
|
|
|
527
698
|
|
|
528
699
|
const filePath = this.filteredFiles[this.selectedIndex];
|
|
529
700
|
if (filePath) {
|
|
530
|
-
this.close();
|
|
701
|
+
this.close({ restoreFocus: false });
|
|
531
702
|
this.onFileSelect?.(filePath);
|
|
532
703
|
}
|
|
533
704
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const DEFAULT_SEARCH_DEBOUNCE_MS = 220;
|
|
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;
|
|
@@ -32,6 +32,12 @@
|
|
|
32
32
|
display 300ms cubic-bezier(0.16, 1, 0.3, 1) allow-discrete;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
.toast[data-tone='warning'] {
|
|
36
|
+
border-color: var(--border-warning-toast);
|
|
37
|
+
background: var(--surface-warning-toast);
|
|
38
|
+
color: var(--text-warning-toast);
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
.toast__message {
|
|
36
42
|
flex: 1 1 auto;
|
|
37
43
|
min-width: 0;
|