feat: add mobile suggestion button and force-focus parameter

- Add "Suggest" button to mobile diff controls with three-column layout
- Replace focusInput's onMobile param with force param for explicit control
- Scope diff mobile button styles to prevent conflicts
- Enable focus on chat input when suggestion button clicked
This commit is contained in:
Andrew Beal 2026-07-05 19:59:07 +01:00
parent f74286f3c8
commit afaa530f8b
6 changed files with 39 additions and 17 deletions

View file

@ -115,9 +115,9 @@
}
}
export function focusInput(onMobile: boolean = false) {
// don't focus on mobile, it's annoying
if (onMobile || !Platform.isMobile) {
export function focusInput(force: boolean = false) {
// Generally don't focus on mobile, it's annoying
if (force || !Platform.isMobile) {
tick().then(() => {
textareaElement?.focus();
});

View file

@ -43,8 +43,8 @@
let currentThought: string | null = null;
export function focusInput() {
chatInput?.focusInput();
export function focusInput(force: boolean = false) {
chatInput?.focusInput(force);
}
export function resetChatArea() {

View file

@ -118,7 +118,7 @@
.diff-view-mobile .diff-mobile-controls {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: 1fr 1fr 1fr;
gap: var(--size-4-2);
position: absolute;
bottom: 0;
@ -128,7 +128,7 @@
z-index: 10;
}
.diff-mobile-button {
.diff-mobile-controls .diff-mobile-button {
min-height: 44px;
font-size: var(--font-ui-medium);
font-weight: var(--font-semibold);
@ -138,7 +138,7 @@
color: var(--text-on-accent);
}
.diff-mobile-accept {
.diff-mobile-controls .diff-mobile-accept {
background-color: color-mix(
in srgb,
var(--color-green) 75%,
@ -146,11 +146,19 @@
);
}
.diff-mobile-accept:active {
.diff-mobile-controls .diff-mobile-accept:active {
background-color: var(--color-green);
}
.diff-mobile-reject {
.diff-mobile-controls .diff-mobile-suggest {
background-color: var(--interactive-accent);
}
.diff-mobile-controls .diff-mobile-suggest:active {
background-color: var(--interactive-accent-hover);
}
.diff-mobile-controls .diff-mobile-reject {
background-color: color-mix(
in srgb,
var(--color-red) 75%,
@ -158,7 +166,7 @@
);
}
.diff-mobile-reject:active {
.diff-mobile-controls .diff-mobile-reject:active {
background-color: var(--color-red);
}

View file

@ -5,7 +5,7 @@ import { Resolve } from "Services/DependencyService";
import type { EventService } from "Services/EventService";
import type { DiffService } from "Services/DiffService";
import { Services } from "Services/Services";
import { VIEW_TYPE_MAIN } from "./MainView";
import { VIEW_TYPE_MAIN, type MainView } from "./MainView";
import { tick } from "svelte";
export const VIEW_TYPE_DIFF = 'vaultkeeper-ai-diff-view';
@ -115,6 +115,12 @@ export class DiffView extends ItemView {
});
acceptButton.setAttribute('aria-label', 'Accept changes');
const suggestButton = container.createEl('button', {
cls: 'diff-mobile-button diff-mobile-suggest',
text: 'Suggest'
});
suggestButton.setAttribute('aria-label', 'Make a suggestion');
const rejectButton = container.createEl('button', {
cls: 'diff-mobile-button diff-mobile-reject',
text: 'Reject'
@ -126,6 +132,10 @@ export class DiffView extends ItemView {
await this.refocusMainView();
});
this.registerDomEvent(suggestButton, 'click', async () => {
await this.refocusMainView(true);
});
this.registerDomEvent(rejectButton, 'click', async () => {
this.diffService.onReject();
await this.refocusMainView();
@ -134,7 +144,7 @@ export class DiffView extends ItemView {
return container;
}
private async refocusMainView(): Promise<void> {
private async refocusMainView(focusChatInput: boolean = false): Promise<void> {
await tick().then(async () => {
const { workspace } = this.app;
const leaves = workspace.getLeavesOfType(VIEW_TYPE_MAIN);
@ -142,6 +152,10 @@ export class DiffView extends ItemView {
if (leaves.length > 0) {
await workspace.revealLeaf(leaves[0]);
workspace.setActiveLeaf(leaves[0], { focus: true });
if (focusChatInput) {
(leaves[0].view as MainView).input?.focusInput(true);
}
}
});
}

View file

@ -9,7 +9,7 @@ import { Services } from 'Services/Services';
export const VIEW_TYPE_MAIN = 'vaultkeeper-ai-main-view';
interface ChatWindowComponent {
focusInput: () => void;
focusInput: (force?: boolean) => void;
resetChatArea: () => void;
}

View file

@ -92,7 +92,7 @@ if (typeof HTMLElement !== 'undefined') {
options?: string | DomElementInfo,
callback?: (el: HTMLElementTagNameMap[K]) => void
): HTMLElementTagNameMap[K] {
const el = document.createElement(tag);
const el = document.createElement(tag) as HTMLElementTagNameMap[K];
applyElementInfo(el, options);
if (this instanceof HTMLElement && el.parentNode !== this) {
this.appendChild(el);
@ -101,9 +101,9 @@ if (typeof HTMLElement !== 'undefined') {
return el;
}
HTMLElement.prototype.createEl = function(tag: string, options?: string | DomElementInfo, callback?: (el: HTMLElement) => void) {
HTMLElement.prototype.createEl = function(this: HTMLElement, tag: string, options?: string | DomElementInfo, callback?: (el: HTMLElement) => void) {
return createElImpl.call(this, tag as keyof HTMLElementTagNameMap, options, callback as any);
};
} as HTMLElement['createEl'];
HTMLElement.prototype.createDiv = function(options?: string | DomElementInfo, callback?: (el: HTMLDivElement) => void) {
return this.createEl('div', options, callback as any);
};