( deps: CaptureTargetScopeDeps, rawCaptureTo: string, captureToActiveFile: boolean, )
| 67 | * legitimately collected pick is never silently dropped. |
| 68 | */ |
| 69 | export function classifyCaptureTargetScope( |
| 70 | deps: CaptureTargetScopeDeps, |
| 71 | rawCaptureTo: string, |
| 72 | captureToActiveFile: boolean, |
| 73 | ): CaptureTargetScope | null { |
| 74 | if (captureToActiveFile) return null; |
| 75 | |
| 76 | const normalizedTarget = (rawCaptureTo ?? "").trim().replace(/^\/+/, ""); |
| 77 | |
| 78 | // A target carrying a format token (e.g. `Projects/{{VALUE}}.md`, |
| 79 | // `Notes/{{DATE}}/`, `#{{VALUE}}`) is only resolvable at run time, so in EVERY |
| 80 | // shape - file, folder, tag, property - it is never a stable picker scope: it |
| 81 | // gets no preflight requirement and the engine never honours a preselected pick |
| 82 | // for it. Returning early keeps that uniform across all branches below. |
| 83 | if (hasTemplatePathSyntax(normalizedTarget)) return null; |
| 84 | |
| 85 | const propertyTarget = parsePropertyTarget(normalizedTarget); |
| 86 | const isPropertyTarget = !!propertyTarget && !!propertyTarget.field; |
| 87 | |
| 88 | const fileFilterTarget = !isPropertyTarget |
| 89 | ? parseCaptureFileFilterTarget(normalizedTarget) |
| 90 | : null; |
| 91 | // A multi-select filter is not a single-destination capture scope. |
| 92 | const isFilterTarget = !!fileFilterTarget && !fileFilterTarget.multiSelect; |
| 93 | |
| 94 | const isTagTarget = |
| 95 | !isPropertyTarget && |
| 96 | !fileFilterTarget && |
| 97 | normalizedTarget.startsWith("#"); |
| 98 | |
| 99 | const endsWithSlash = normalizedTarget.endsWith("/"); |
| 100 | |
| 101 | // A target carrying a recognized file extension with no trailing slash is never |
| 102 | // a runtime-pick scope. This mirrors resolveCaptureTarget, which tests the |
| 103 | // extension BEFORE the folder-ambiguity check, so an extensioned target that |
| 104 | // happens to collide with a same-named folder is NOT misrouted to that folder |
| 105 | // in either classifier. `.md`/`.canvas` are definite files; `.base` is |
| 106 | // unsupported (the resolver throws on it downstream) - in all three cases the |
| 107 | // engine must NOT honour an injected `__qa.captureTargetFilePath`, so returning |
| 108 | // `null` here both stops the spurious folder prompt AND preserves the #1448 |
| 109 | // reserved-key confinement for a configured definite/unsupported file target. |
| 110 | const hasRecognizedFileExtension = |
| 111 | !isTagTarget && |
| 112 | !isPropertyTarget && |
| 113 | !isFilterTarget && |
| 114 | !endsWithSlash && |
| 115 | (MARKDOWN_FILE_EXTENSION_REGEX.test(normalizedTarget) || |
| 116 | CANVAS_FILE_EXTENSION_REGEX.test(normalizedTarget) || |
| 117 | BASE_FILE_EXTENSION_REGEX.test(normalizedTarget)); |
| 118 | |
| 119 | const folderProbePath = normalizedTarget.replace(/\/+$/, ""); |
| 120 | // A bare name targets a folder only when the folder exists AND no same-name |
| 121 | // note does (resolveCaptureTarget's disambiguation; docs: "if both Projects/ |
| 122 | // and Projects.md exist, Projects targets Projects.md"). Probing for the note |
| 123 | // keeps the classifier from offering a folder pick - and honouring an injected |
| 124 | // pick - for a bare name the write path resolves to a definite file. An empty |
| 125 | // target is the whole-vault scope and needs no probe. A trailing-slash target |
| 126 | // forces the folder regardless (handled by looksLikeFolderBySuffix below). |
no test coverage detected