fix: make ai panel shortcut command-only

This commit is contained in:
lucaronin
2026-04-09 13:16:35 +02:00
parent dd78e254ff
commit 31c8ac4774
5 changed files with 47 additions and 8 deletions

View File

@@ -295,7 +295,7 @@ fn build_note_menu(app: &App) -> MenuResult {
.build(app)?;
let toggle_ai_chat = MenuItemBuilder::new("Toggle AI Panel")
.id(VIEW_TOGGLE_AI_CHAT)
.accelerator("CmdOrCtrl+Shift+L")
.accelerator("Cmd+Shift+L")
.build(app)?;
let toggle_backlinks = MenuItemBuilder::new("Toggle Backlinks")
.id(VIEW_TOGGLE_BACKLINKS)

View File

@@ -38,8 +38,10 @@ const VIEW_MODE_KEYS: Record<string, ViewMode> = {
}
function isTextInputFocused(): boolean {
const tag = document.activeElement?.tagName
return tag === 'INPUT' || tag === 'TEXTAREA'
const active = document.activeElement
if (!(active instanceof HTMLElement)) return false
if (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA') return true
return active.isContentEditable || active.closest('[contenteditable="true"]') !== null
}
function isCommandOrCtrlOnly(e: KeyboardEvent): boolean {

View File

@@ -3,6 +3,14 @@ import { renderHook } from '@testing-library/react'
import { useAppKeyboard } from './useAppKeyboard'
function fireKey(key: string, mods: { altKey?: boolean; metaKey?: boolean; ctrlKey?: boolean; shiftKey?: boolean } = {}) {
fireKeyOnTarget(window, key, mods)
}
function fireKeyOnTarget(
target: EventTarget,
key: string,
mods: { altKey?: boolean; metaKey?: boolean; ctrlKey?: boolean; shiftKey?: boolean } = {},
) {
const event = new KeyboardEvent('keydown', {
key,
altKey: mods.altKey ?? false,
@@ -12,7 +20,7 @@ function fireKey(key: string, mods: { altKey?: boolean; metaKey?: boolean; ctrlK
bubbles: true,
cancelable: true,
})
window.dispatchEvent(event)
target.dispatchEvent(event)
}
function makeActions() {
@@ -130,6 +138,14 @@ describe('useAppKeyboard', () => {
try { fn() } finally { document.body.removeChild(input) }
}
function withFocusedContentEditable(fn: (editable: HTMLDivElement) => void) {
const editable = document.createElement('div')
editable.setAttribute('contenteditable', 'true')
document.body.appendChild(editable)
editable.focus()
try { fn(editable) } finally { document.body.removeChild(editable) }
}
it('Cmd+Backspace does not delete note when text input is focused', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
@@ -139,6 +155,15 @@ describe('useAppKeyboard', () => {
})
})
it('Cmd+Backspace does not delete note when contenteditable is focused', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
withFocusedContentEditable((editable) => {
fireKeyOnTarget(editable, 'Backspace', { metaKey: true })
expect(actions.onDeleteNote).not.toHaveBeenCalled()
})
})
it('Cmd+Backspace deletes note when no text input is focused', () => {
const actions = makeActions()
renderHook(() => useAppKeyboard(actions))
@@ -201,6 +226,17 @@ describe('useAppKeyboard', () => {
})
})
it('Cmd+Shift+L works when editor stops propagation', () => {
const actions = makeActions()
const onToggleAIChat = vi.fn()
renderHook(() => useAppKeyboard({ ...actions, onToggleAIChat }))
withFocusedContentEditable((editable) => {
editable.addEventListener('keydown', (event) => event.stopPropagation())
fireKeyOnTarget(editable, 'l', { metaKey: true, shiftKey: true })
expect(onToggleAIChat).toHaveBeenCalled()
})
})
it('Ctrl+Shift+L does not trigger toggle AI chat', () => {
const actions = makeActions()
const onToggleAIChat = vi.fn()

View File

@@ -14,7 +14,7 @@ export function useAppKeyboard(actions: KeyboardActions) {
onKeyDown(event)
}
window.addEventListener('keydown', handleWindowKeyDown)
return () => window.removeEventListener('keydown', handleWindowKeyDown)
window.addEventListener('keydown', handleWindowKeyDown, true)
return () => window.removeEventListener('keydown', handleWindowKeyDown, true)
}, [])
}

View File

@@ -8,9 +8,10 @@ test.describe('AI panel shortcut', () => {
await expect(page.locator('[data-testid="note-list-container"]')).toBeVisible({ timeout: 5_000 })
})
test('Cmd/Ctrl+Shift+L opens the AI panel', async ({ page }) => {
test('Cmd+Shift+L opens the AI panel from the editor', async ({ page }) => {
await page.locator('.app__note-list .cursor-pointer').first().click()
await sendShortcut(page, 'L', ['Control', 'Shift'])
await page.locator('.bn-editor').click()
await sendShortcut(page, 'L', ['Meta', 'Shift'])
await expect(page.getByTestId('ai-panel')).toBeVisible({ timeout: 3_000 })
await expect(page.getByTitle('Close AI panel')).toBeVisible()
})