Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/formatters/formatSyntax.docs-examples.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,13 +300,15 @@ describe("Format Syntax documentation examples", () => {
displayOptions: ["Low", "Normal", "High"],
});
expect(
collector.requirements.get("status|default:Draft|default-always:true"),
collector.requirements.get(
"FIELD:status|default:Draft|default-always:true",
),
).toMatchObject({
type: "field-suggest",
});
expect(
collector.requirements.get(
"Id|inline:true|inline-code-blocks:ad-note",
"FIELD:Id|inline:true|inline-code-blocks:ad-note",
),
).toMatchObject({
type: "field-suggest",
Expand Down
8 changes: 2 additions & 6 deletions src/formatters/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
type ValueInputType,
} from "../utils/valueSyntax";
import { parseMacroToken } from "../utils/macroSyntax";
import { getFieldVariableKey } from "../utils/fieldVariableKey";

export type LinkToCurrentFileBehavior = "required" | "optional";

Expand All @@ -54,7 +55,6 @@ export abstract class Formatter {
protected variables: Map<string, unknown> = new Map<string, unknown>();
protected dateParser: IDateParser | undefined;
private linkToCurrentFileBehavior: LinkToCurrentFileBehavior = "required";
private static readonly FIELD_VARIABLE_PREFIX = "FIELD:";
protected valuePromptContext?: PromptContext;

// Tracks variables collected for YAML property post-processing
Expand Down Expand Up @@ -443,7 +443,7 @@ export abstract class Formatter {
const fullMatch = match[1] + (match[2] || "");

if (fullMatch) {
const fieldVariableKey = this.getFieldVariableKey(fullMatch);
const fieldVariableKey = getFieldVariableKey(fullMatch);

if (!this.hasConcreteVariable(fieldVariableKey)) {
this.variables.set(
Expand All @@ -469,10 +469,6 @@ export abstract class Formatter {
return output;
}

private getFieldVariableKey(fieldSpecifier: string): string {
return `${Formatter.FIELD_VARIABLE_PREFIX}${fieldSpecifier}`;
}

protected abstract promptForMathValue(): Promise<string>;

protected async replaceMathValueInString(input: string) {
Expand Down
9 changes: 4 additions & 5 deletions src/gui/suggesters/FieldValueInputSuggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import {
FieldSuggestionParser,
type FieldFilter,
} from "src/utils/FieldSuggestionParser";
import {
collectFieldValuesProcessed
} from "src/utils/FieldValueCollector";
import { collectFieldValuesProcessed } from "src/utils/FieldValueCollector";
import { stripFieldVariableKeyPrefix } from "src/utils/fieldVariableKey";
import { TextInputSuggest } from "./suggest";

export class FieldValueInputSuggest extends TextInputSuggest<string> {
Expand All @@ -16,8 +15,8 @@ export class FieldValueInputSuggest extends TextInputSuggest<string> {

constructor(app: App, inputEl: HTMLInputElement, fieldInput: string) {
super(app, inputEl);
this.fieldInput = fieldInput;
const parsed = FieldSuggestionParser.parse(fieldInput);
this.fieldInput = stripFieldVariableKeyPrefix(fieldInput);
const parsed = FieldSuggestionParser.parse(this.fieldInput);
this.fieldName = parsed.fieldName;
this.filters = parsed.filters;
}
Expand Down
29 changes: 29 additions & 0 deletions src/preflight/OnePageInputModal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,35 @@ describe("OnePageInputModal", () => {
});
});

it("submits FIELD values under the prefixed runtime key", async () => {
const id = "FIELD:People";
const requirements: FieldRequirement[] = [
{
id,
label: "People",
type: "field-suggest",
},
];

const modal = new OnePageInputModal({} as App, requirements, new Map());
const input = (modal as any).contentEl.querySelector(
"input",
) as HTMLInputElement;
input.value = "Alice";
input.dispatchEvent(new Event("input", { bubbles: true }));
const submitButton = Array.from(
(modal as any).contentEl.querySelectorAll(
"button",
) as NodeListOf<HTMLButtonElement>,
).find((button) => button.textContent === "Submit") as HTMLButtonElement;

submitButton.click();

await expect(modal.waitForClose).resolves.toEqual({
[id]: "Alice",
});
});

// Regression: issue #1180 — One-page input dropped VALUE dropdown
// selections for labeled tokens like {{VALUE:option-a,option-b|label:Pick one}},
// resulting in an empty captured value instead of the first option.
Expand Down
28 changes: 28 additions & 0 deletions src/preflight/RequirementCollector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,32 @@ Body`);
const requirement = rc.requirements.get("title");
expect(requirement?.type).toBe("textarea");
});

it("collects FIELD requirements with runtime-compatible ids and raw labels", async () => {
const app = makeApp();
const plugin = makePlugin();
const rc = new RequirementCollector(app, plugin);
await rc.scanString("Person: {{FIELD:People}}");

expect(rc.requirements.get("People")).toBeUndefined();
expect(rc.requirements.get("FIELD:People")).toMatchObject({
id: "FIELD:People",
label: "People",
type: "field-suggest",
});
});

it("preserves filtered FIELD syntax in the prefixed runtime id", async () => {
const app = makeApp();
const plugin = makePlugin();
const rc = new RequirementCollector(app, plugin);
await rc.scanString("Person: {{FIELD:People|folder:Contacts}}");

expect(rc.requirements.get("People|folder:Contacts")).toBeUndefined();
expect(rc.requirements.get("FIELD:People|folder:Contacts")).toMatchObject({
id: "FIELD:People|folder:Contacts",
label: "People|folder:Contacts",
type: "field-suggest",
});
});
});
8 changes: 5 additions & 3 deletions src/preflight/RequirementCollector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Formatter, type PromptContext } from "src/formatters/formatter";
import type { IChoiceExecutor } from "src/IChoiceExecutor";
import type QuickAdd from "src/main";
import { NLDParser } from "src/parsers/NLDParser";
import { getFieldVariableKey } from "src/utils/fieldVariableKey";
import { parseValueToken } from "src/utils/valueSyntax";

export type FieldType =
Expand Down Expand Up @@ -284,9 +285,10 @@ export class RequirementCollector extends Formatter {

protected async suggestForField(variableName: string): Promise<string> {
// Store as a field-suggest requirement; actual suggestions are provided by UI
if (!this.requirements.has(variableName)) {
this.requirements.set(variableName, {
id: variableName,
const key = getFieldVariableKey(variableName);
if (!this.requirements.has(key)) {
this.requirements.set(key, {
id: key,
label: variableName,
type: "field-suggest",
source: "collected",
Expand Down
20 changes: 19 additions & 1 deletion src/preflight/collectChoiceRequirements.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import type ICaptureChoice from "src/types/choices/ICaptureChoice";
import type IMacroChoice from "src/types/choices/IMacroChoice";
import { CommandType } from "src/types/macros/CommandType";
import type { IUserScript } from "src/types/macros/IUserScript";
import { collectChoiceRequirements } from "./collectChoiceRequirements";
import {
collectChoiceRequirements,
getUnresolvedRequirements,
} from "./collectChoiceRequirements";

const {
getMarkdownFilesInFolderMock,
Expand Down Expand Up @@ -265,3 +268,18 @@ describe("collectChoiceRequirements - capture targets", () => {
).toBe(false);
});
});

describe("getUnresolvedRequirements - FIELD keys", () => {
it("considers prefixed FIELD values resolved", () => {
const requirements = [
{
id: "FIELD:People",
label: "People",
type: "field-suggest" as const,
},
];
const variables = new Map<string, unknown>([["FIELD:People", "Alice"]]);

expect(getUnresolvedRequirements(requirements, variables)).toEqual([]);
});
});
25 changes: 25 additions & 0 deletions src/utils/fieldVariableKey.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import {
getFieldVariableKey,
stripFieldVariableKeyPrefix,
} from "./fieldVariableKey";

describe("fieldVariableKey", () => {
it("builds FIELD-prefixed runtime keys", () => {
expect(getFieldVariableKey("People|folder:Contacts")).toBe(
"FIELD:People|folder:Contacts",
);
});

it("strips FIELD prefixes before field suggestion parsing", () => {
expect(stripFieldVariableKeyPrefix("FIELD:People|folder:Contacts")).toBe(
"People|folder:Contacts",
);
});

it("leaves raw field suggestion inputs unchanged", () => {
expect(stripFieldVariableKeyPrefix("People|folder:Contacts")).toBe(
"People|folder:Contacts",
);
});
});
11 changes: 11 additions & 0 deletions src/utils/fieldVariableKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const FIELD_VARIABLE_PREFIX = "FIELD:";

export function getFieldVariableKey(fieldSpecifier: string): string {
return `${FIELD_VARIABLE_PREFIX}${fieldSpecifier}`;
}

export function stripFieldVariableKeyPrefix(fieldInput: string): string {
return fieldInput.startsWith(FIELD_VARIABLE_PREFIX)
? fieldInput.slice(FIELD_VARIABLE_PREFIX.length)
: fieldInput;
}
Loading