feat(plugin): multi-line text input via ui.input-multiline (#1968)

* feat(plugin): multi-line text input via ui.input-multiline

Adds host::input_multiline: a multi-line text dialog pre-filled with a
value, dismissed to none, confirmed with Cmd/Ctrl+Enter. Same rules as
input (command or event of a window, no capability, wait not charged).

Prompt::Input gains a multiline flag the frontend renders as a
textarea; the jobs fixture, SDK (wasm + mock), WIT and the plugin API
table are updated, and all plugin wasms rebuilt.

Fixes #1946

* chore: regenerate bindings after merging detail-provider rename
Цей коміт міститься в:
arookieofc authored and GitHub committed 2026-11-07 19:58:32 +08:00
1 джерело e68b35f534
коміт 2945d20208
54 files changed
+160 -43

No files matched your search

+23 -3
Переглянути файл
@@ -536,13 +536,33 @@ describe("plugin prompts", () => {
type: "promptRequested",
pluginId: "p",
promptId: "2",
prompt: { kind: "input", prompt: "Name", value: "Main" },
prompt: { kind: "input", prompt: "Name", value: "Main", multiline: false },
});
});
await user.click(await screen.findByRole("button", { name: "Cancel" }));
await waitFor(() => expect(answerPrompt).toHaveBeenCalledWith("2", null));
});
it("asks multi-line text and confirms on Ctrl+Enter", async () => {
const user = userEvent.setup();
const answerPrompt = vi.fn().mockResolvedValue(undefined);
setPluginsBackend({ ...createMemoryPluginsBackend(), answerPrompt });
render(<InputDialogsHost />);
act(() => {
handlePluginEvent({
type: "promptRequested",
pluginId: "p",
promptId: "5",
prompt: { kind: "input", prompt: "Notes", value: "one", multiline: true },
});
});
const area = await screen.findByRole("textbox");
expect(area.tagName).toBe("TEXTAREA");
await user.clear(area);
await user.type(area, "a{Enter}b{Control>}{Enter}{/Control}");
await waitFor(() => expect(answerPrompt).toHaveBeenCalledWith("5", ["a\nb"]));
});
it("returns the typed text untrimmed", async () => {
const user = userEvent.setup();
const answerPrompt = vi.fn().mockResolvedValue(undefined);
@@ -553,7 +573,7 @@ describe("plugin prompts", () => {
type: "promptRequested",
pluginId: "p",
promptId: "4",
prompt: { kind: "input", prompt: "Name", value: " Main " },
prompt: { kind: "input", prompt: "Name", value: " Main ", multiline: false },
});
});
await user.click(await screen.findByRole("button", { name: "OK" }));
@@ -569,7 +589,7 @@ describe("plugin prompts", () => {
type: "promptRequested",
pluginId: "p",
promptId: "3",
prompt: { kind: "input", prompt: "Name", value: null },
prompt: { kind: "input", prompt: "Name", value: null, multiline: false },
});
});
await screen.findByRole("dialog");
+1
Переглянути файл
@@ -327,6 +327,7 @@ async function askUser(prompt: Prompt, signal: AbortSignal): Promise<string[] |
initial: prompt.value ?? "",
validate: () => null,
keepWhitespace: true,
multiline: prompt.multiline,
signal,
});
return text === null ? null : [text];