* Shared picker for the "anywhere in the vault" capture scopes (tag, property): * the matched notes can live in any folder, so the picker shows full paths. The * "Create new note" affordance is suppressed for any name that already exists * in the vault (by path or basename, in any folder), so
( files: TFile[], notFoundMessage: string, )
| 1272 | * captures into that file, and never spawns a duplicate-basename note. |
| 1273 | */ |
| 1274 | private async selectFileFromSet( |
| 1275 | files: TFile[], |
| 1276 | notFoundMessage: string, |
| 1277 | ): Promise<string> { |
| 1278 | const allowCreate = this.choice.createFileIfItDoesntExist?.enabled ?? false; |
| 1279 | |
| 1280 | invariant(allowCreate || files.length > 0, notFoundMessage); |
| 1281 | |
| 1282 | // See selectFileInFolder: a format-syntax tag/property capture target resolves |
| 1283 | // to a runtime file picker the requirement collector can't pre-collect. Placed |
| 1284 | // after the no-match check so an empty result surfaces its own accurate error. |
| 1285 | this.assertInteractiveCaptureTarget(); |
| 1286 | |
| 1287 | // Quick-Switcher-style ordering; show note names (not raw paths). |
| 1288 | const orderedFiles = orderFilesForPicker( |
| 1289 | files, |
| 1290 | buildPickerOrderingDeps(this.app), |
| 1291 | ); |
| 1292 | const filePaths = orderedFiles.map((f) => f.path); |
| 1293 | const displayItems = buildFileDisplayLabels( |
| 1294 | orderedFiles, |
| 1295 | (file) => this.app.metadataCache.getFileCache(file), |
| 1296 | ); |
| 1297 | const searchItems = filePaths.map( |
| 1298 | (path, index) => `${displayItems[index] ?? path} ${path}`, |
| 1299 | ); |
| 1300 | // Build once (not per keystroke): existing note basenames across the vault. |
| 1301 | const vaultBasenames = new Set( |
| 1302 | this.app.vault |
| 1303 | .getMarkdownFiles() |
| 1304 | .map((f) => f.basename.toLowerCase()), |
| 1305 | ); |
| 1306 | const existingLabels = new Set( |
| 1307 | displayItems.map((label) => label.toLowerCase()), |
| 1308 | ); |
| 1309 | let targetFilePath: string; |
| 1310 | try { |
| 1311 | targetFilePath = await InputSuggester.Suggest( |
| 1312 | this.app, |
| 1313 | displayItems, |
| 1314 | filePaths, |
| 1315 | { |
| 1316 | placeholder: allowCreate |
| 1317 | ? "Choose a note or type to create one" |
| 1318 | : undefined, |
| 1319 | emptyStateText: allowCreate |
| 1320 | ? "Type a note name to create it" |
| 1321 | : undefined, |
| 1322 | renderItem: (path, el) => |
| 1323 | renderNotePathSuggestion(el, path, this.app), |
| 1324 | searchItems, |
| 1325 | allowCustomValue: allowCreate, |
| 1326 | customValueLabel: (value) => `Create new note: ${value}`, |
| 1327 | valueExists: (value) => |
| 1328 | existingLabels.has(value.toLowerCase()) || |
| 1329 | this.captureTargetAlreadyExists(value, vaultBasenames), |
| 1330 | }, |
| 1331 | ); |
no test coverage detected