Skip to content
Merged
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
4 changes: 2 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Polling continues while a prompt is open. The poll is the server's only sign tha
- A select or multi field with `allowCustomInput` gets a text field for values outside the list. For multi, the text field takes comma-separated values.
- As in QuickAdd's one-page form, a single-note picker (`picker: "file"`) starts with no note picked, and the form won't submit until a required one has a pick.

Text fields are controlled. When an edit ends in a new `[[`, or types `#` at the start of a word, the field pushes `LinkPicker` or `TagPicker` (`src/completion-pickers.tsx`), which loads its items from `quickadd:suggest`. A pick replaces the trigger with `[[text]]` or `#tag `, and the field takes focus again when the picker closes. The trigger rules are pure functions in `src/lib/completion.ts`.
Text and number fields are controlled, because Raycast clears an uncontrolled text field when the form re-renders with its error. When an edit to a text field ends in a new `[[`, or types `#` at the start of a word, the field pushes `LinkPicker` or `TagPicker` (`src/completion-pickers.tsx`), which loads its items from `quickadd:suggest`. A pick replaces the trigger with `[[text]]` or `#tag `, and the field takes focus again when the picker closes. The trigger rules are pure functions in `src/lib/completion.ts`.

The suggester, confirm, checkbox, and info prompts keep their own views. A suggester is a searchable list, which suits a single pick from many items.

Expand All @@ -79,6 +79,6 @@ Form item ids are positional (`field-0`), not field ids, because QuickAdd field

The `verify` flag that **Run in Obsidian** and the capture commands pass needs **QuickAdd >= 2.14**. Older versions ignore it, and some captures can report success without writing.

`[[` and `#` completion needs **QuickAdd with `quickadd:suggest`**, which is not released yet. Without it, the picker shows the CLI's error.
`[[` and `#` completion needs **QuickAdd >= 2.31**, which added `quickadd:suggest`. Before it, the picker says which version it needs.

In the one-page form, note pickers start empty only with **QuickAdd >= 2.31**, which marks them with `picker: "file"`. Older versions send them as plain suggesters, so they keep the first note picked.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The form renders each input from the field metadata QuickAdd sends. To get a lar

### Links and tags

Typing `[[` in a text field opens a searchable list of the vault's notes and aliases. Typing `#` at the start of a word opens the vault's tags, most used first. Picking one inserts the link or tag where you typed. This needs QuickAdd with `quickadd:suggest`.
Typing `[[` in a text field opens a searchable list of the vault's notes and aliases. Typing `#` at the start of a word opens the vault's tags, most used first. Picking one inserts the link or tag where you typed. This needs QuickAdd 2.31 or later.

## Requirements

Expand Down
36 changes: 29 additions & 7 deletions src/form-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,7 @@ export function FieldControl({
case "text":
return <TextControl {...common} spec={spec} vault={vault} />;
case "number":
return (
<Form.TextField
{...common}
placeholder={spec.placeholder ?? "Number"}
defaultValue={spec.defaultValue}
/>
);
return <NumberControl {...common} spec={spec} />;
case "date":
return (
<Form.DatePicker
Expand Down Expand Up @@ -117,6 +111,34 @@ export function FieldControl({
}
}

// Controlled: Raycast clears an uncontrolled text field when the form
// re-renders with its error, so a rejected value would vanish.
function NumberControl({
spec,
onChange,
...item
}: {
spec: Extract<FieldSpec, { kind: "number" }>;
id: string;
title: string;
info?: string;
error?: string;
onChange: () => void;
}) {
const [value, setValue] = useState(spec.defaultValue ?? "");
return (
<Form.TextField
{...item}
placeholder={spec.placeholder ?? "Number"}
value={value}
onChange={(next) => {
onChange();
setValue(next);
}}
/>
);
}

function TextControl({
spec,
vault,
Expand Down
33 changes: 33 additions & 0 deletions src/lib/suggest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it, vi } from "vitest";
import { suggestTags } from "./suggest";
import { vaultAt } from "./vaults";

const cli = vi.hoisted(() => ({ stdout: "" }));

vi.mock("@raycast/api", () => ({
getPreferenceValues: () => ({ cliPath: process.execPath }),
}));

vi.mock("node:child_process", () => ({
execFile: (...args: unknown[]) => {
const callback = args.at(-1) as (error: null, out: object) => void;
callback(null, { stdout: cli.stdout });
},
}));

describe("suggestTags", () => {
const vault = vaultAt("/Users/me/notes");

it("says which QuickAdd it needs when the CLI lacks quickadd:suggest", async () => {
cli.stdout =
'Error: Command "quickadd:suggest" not found. It may require a plugin to be enabled.\n';
await expect(suggestTags(vault)).rejects.toThrow(
/^Link and tag completion needs QuickAdd 2\.31 or later\.$/,
);
});

it("passes other CLI errors through unchanged", async () => {
cli.stdout = "Vault not found.\n";
await expect(suggestTags(vault)).rejects.toThrow(/^Vault not found\.$/);
});
});
19 changes: 16 additions & 3 deletions src/lib/suggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,22 @@ interface SuggestResponse<T> {
}

async function suggest<T>(vault: Vault, kind: "links" | "tags"): Promise<T[]> {
const response = await invoke<SuggestResponse<T>>(vault, "quickadd:suggest", {
kind,
});
let response: SuggestResponse<T>;
try {
response = await invoke<SuggestResponse<T>>(vault, "quickadd:suggest", {
kind,
});
} catch (error) {
if (
error instanceof ObsidianCliError &&
error.message.includes('Command "quickadd:suggest" not found')
) {
throw new ObsidianCliError(
"Link and tag completion needs QuickAdd 2.31 or later.",
);
}
throw error;
}
if (!response.ok || !response.items) {
throw new ObsidianCliError(
response.error ?? "QuickAdd returned no suggestions.",
Expand Down
Loading