mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Keep composer suggestions closed after insertion
This commit is contained in:
parent
1b1107927a
commit
e6c13640cd
2 changed files with 88 additions and 5 deletions
|
|
@ -66,7 +66,7 @@ export class ChatComposerController {
|
|||
registerEvent(this.options.app.vault.on("modify", invalidate));
|
||||
}
|
||||
|
||||
render(parent: HTMLElement): void {
|
||||
render(parent: HTMLElement, options: { updateSuggestions?: boolean } = {}): void {
|
||||
this.parent = parent;
|
||||
const state = this.state;
|
||||
const elements = renderComposerShell(
|
||||
|
|
@ -119,7 +119,7 @@ export class ChatComposerController {
|
|||
);
|
||||
this.composer = elements.composer;
|
||||
syncComposerHeight(this.composer);
|
||||
this.updateSuggestions({ renderOnChange: true });
|
||||
if (options.updateSuggestions !== false) this.updateSuggestions({ renderOnChange: true });
|
||||
}
|
||||
|
||||
setDraft(text: string, options: { focus?: boolean; clearSuggestions?: boolean; renderIfDetached?: boolean } = {}): void {
|
||||
|
|
@ -151,9 +151,9 @@ export class ChatComposerController {
|
|||
this.parent = null;
|
||||
}
|
||||
|
||||
refreshControls(parent: HTMLElement | null = this.parent): void {
|
||||
refreshControls(parent: HTMLElement | null = this.parent, options: { updateSuggestions?: boolean } = {}): void {
|
||||
if (!parent) return;
|
||||
this.render(parent);
|
||||
this.render(parent, options);
|
||||
}
|
||||
|
||||
codexInput(text: string): UserInput[] {
|
||||
|
|
@ -257,7 +257,7 @@ export class ChatComposerController {
|
|||
|
||||
this.dispatch({ type: "composer/draft-set", draft: insertion.value, clearSuggestions: true });
|
||||
this.options.onDraftChange();
|
||||
this.refreshControls();
|
||||
this.refreshControls(this.parent, { updateSuggestions: false });
|
||||
syncComposerHeight(this.composer);
|
||||
this.composer.focus();
|
||||
this.composer.setSelectionRange(insertion.cursor, insertion.cursor);
|
||||
|
|
|
|||
83
tests/features/chat/chat-composer-controller.test.ts
Normal file
83
tests/features/chat/chat-composer-controller.test.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// @vitest-environment jsdom
|
||||
|
||||
import type { App } from "obsidian";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { ChatComposerController } from "../../../src/features/chat/chat-composer-controller";
|
||||
import { createChatStateStore } from "../../../src/features/chat/chat-state";
|
||||
import type { SkillMetadata } from "../../../src/generated/app-server/v2/SkillMetadata";
|
||||
import { installObsidianDomShims } from "../../support/dom";
|
||||
|
||||
installObsidianDomShims();
|
||||
|
||||
describe("ChatComposerController", () => {
|
||||
it("keeps suggestions closed after inserting at a cursor before later trigger text", () => {
|
||||
const stateStore = createChatStateStore();
|
||||
stateStore.dispatch({ type: "thread/list-applied", availableSkills: [skill("obsidian-search")] });
|
||||
stateStore.dispatch({ type: "composer/draft-set", draft: "/pla then $ob" });
|
||||
const parent = document.createElement("div");
|
||||
const controller = new ChatComposerController({
|
||||
app: app(),
|
||||
stateStore,
|
||||
viewId: "view",
|
||||
sendShortcut: () => "enter",
|
||||
scrollThreadFromComposerEdges: () => false,
|
||||
canInterrupt: () => false,
|
||||
composerPlaceholder: () => "Ask Codex to work on this task...",
|
||||
currentModelForSuggestions: () => null,
|
||||
renderIfDetached: vi.fn(),
|
||||
onDraftChange: vi.fn(),
|
||||
onComposerResize: vi.fn(),
|
||||
onSubmit: vi.fn(),
|
||||
onNewThread: vi.fn(),
|
||||
onThreadScrollFromComposer: vi.fn(),
|
||||
});
|
||||
|
||||
controller.render(parent);
|
||||
composer(parent).setSelectionRange(4, 4);
|
||||
composer(parent).dispatchEvent(new KeyboardEvent("keyup", { bubbles: true, key: "a" }));
|
||||
|
||||
const planSuggestion = expectPresent(parent.querySelector<HTMLElement>(".codex-panel__composer-suggestion"));
|
||||
expect(planSuggestion.textContent).toContain("/plan");
|
||||
|
||||
planSuggestion.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
||||
|
||||
expect(composer(parent).value).toBe("/plan then $ob");
|
||||
expect(composer(parent).selectionStart).toBe("/plan".length);
|
||||
expect(stateStore.getState().composerSuggestions).toEqual([]);
|
||||
expect(composer(parent).getAttribute("aria-expanded")).toBe("false");
|
||||
expect(composer(parent).hasAttribute("aria-activedescendant")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
function app(): App {
|
||||
return {
|
||||
workspace: {
|
||||
getActiveFile: () => null,
|
||||
getLastOpenFiles: () => [],
|
||||
},
|
||||
vault: {
|
||||
getFiles: () => [],
|
||||
},
|
||||
} as unknown as App;
|
||||
}
|
||||
|
||||
function skill(name: string): SkillMetadata {
|
||||
return {
|
||||
name,
|
||||
description: `${name} description`,
|
||||
path: `/vault/skills/${name}/SKILL.md`,
|
||||
scope: "repo",
|
||||
enabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
function composer(parent: HTMLElement): HTMLTextAreaElement {
|
||||
return expectPresent(parent.querySelector<HTMLTextAreaElement>(".codex-panel__composer-input"));
|
||||
}
|
||||
|
||||
function expectPresent<T>(value: T | null | undefined): T {
|
||||
expect(value).not.toBeNull();
|
||||
expect(value).not.toBeUndefined();
|
||||
return value as T;
|
||||
}
|
||||
Loading…
Reference in a new issue