Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { Present } from '@gouvfr-lasuite/ui-kit/icons';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';

import AddLinkSVG from '@/assets/icons/ui-kit/add_link.svg';
Expand All @@ -32,6 +32,7 @@ import {
useDocUtils,
useDuplicateDoc,
} from '@/docs/doc-management';
import { usePresenterToggleShortcut } from '@/docs/doc-presenter/hooks/usePresenterToggleShortcut';
import { useAuth } from '@/features/auth';
import { useFocusStore, useResponsiveStore } from '@/stores';

Expand Down Expand Up @@ -113,6 +114,15 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const { restoreFocus, addLastFocus } = useFocusStore();
const { isMobile } = useResponsiveStore();
const copyDocLink = useCopyDocLink(doc.id);

const togglePresenter = useCallback(() => {
setIsPresenterOpen((isOpen) => !isOpen);
}, []);
usePresenterToggleShortcut(
togglePresenter,
!doc.deleted_at && !isMobile, // same conditions as the "Present" menu entry
);

const { mutate: duplicateDoc } = useDuplicateDoc({
onSuccess: (data) => {
void router.push(`/docs/${data.id}`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { renderHook } from '@testing-library/react';
import { describe, expect, test, vi } from 'vitest';

import { usePresenterToggleShortcut } from '../hooks/usePresenterToggleShortcut';

const renderShortcut = (enabled?: boolean) => {
const onToggle = vi.fn();
renderHook(() => usePresenterToggleShortcut(onToggle, enabled));
return onToggle;
};

const press = (init: KeyboardEventInit) => {
const event = new KeyboardEvent('keydown', { ...init, cancelable: true });
window.dispatchEvent(event);
return event;
};

describe('usePresenterToggleShortcut', () => {
test('Ctrl+Alt+P and Meta+Alt+P call onToggle', () => {
const onToggle = renderShortcut();
press({ code: 'KeyP', ctrlKey: true, altKey: true });
press({ code: 'KeyP', metaKey: true, altKey: true });
expect(onToggle).toHaveBeenCalledTimes(2);
});

test('Mod+Alt+P prevents default', () => {
renderShortcut();
const event = press({ code: 'KeyP', ctrlKey: true, altKey: true });
expect(event.defaultPrevented).toBe(true);
});

test('KeyP without the full modifier combination is ignored', () => {
const onToggle = renderShortcut();
press({ code: 'KeyP' });
press({ code: 'KeyP', ctrlKey: true });
press({ code: 'KeyP', altKey: true });
press({ code: 'KeyP', ctrlKey: true, altKey: true, shiftKey: true });
expect(onToggle).not.toHaveBeenCalled();
});

test('repeat events are ignored', () => {
const onToggle = renderShortcut();
press({ code: 'KeyP', ctrlKey: true, altKey: true, repeat: true });
expect(onToggle).not.toHaveBeenCalled();
});

test('does nothing when disabled', () => {
const onToggle = renderShortcut(false);
press({ code: 'KeyP', ctrlKey: true, altKey: true });
expect(onToggle).not.toHaveBeenCalled();
});
});
Comment on lines +18 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add a regression test to ensure AltGraph does not trigger presenter toggle.

Please add a non-trigger case so keyboard-layout regressions are caught when ctrlKey + altKey comes from AltGraph.

Suggested test addition
   test('does nothing when disabled', () => {
     const onToggle = renderShortcut(false);
     press({ code: 'KeyP', ctrlKey: true, altKey: true });
     expect(onToggle).not.toHaveBeenCalled();
   });
+
+  test('AltGraph+P is ignored', () => {
+    const onToggle = renderShortcut();
+    const event = press({ code: 'KeyP', ctrlKey: true, altKey: true });
+    Object.defineProperty(event, 'getModifierState', {
+      value: (key: string) => key === 'AltGraph',
+    });
+    window.dispatchEvent(event);
+    expect(onToggle).not.toHaveBeenCalled();
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/frontend/apps/impress/src/features/docs/doc-presenter/__tests__/usePresenterToggleShortcut.spec.ts`
around lines 18 - 52, Add a regression test that ensures AltGraph doesn't
trigger the presenter toggle: in the existing usePresenterToggleShortcut tests
(referencing renderShortcut and the press helper), add a new test that calls
renderShortcut(), then calls press({ code: 'KeyP', ctrlKey: true, altKey: true,
altGraph: true }) and asserts the onToggle mock was not called; keep the test
name descriptive (e.g., 'AltGraph does not trigger presenter toggle') and mirror
the pattern of the other tests.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect } from 'react';

/**
* Toggles the presenter on Mod+Alt+P (Ctrl+Alt+P / ⌘⌥P).
* Matching on `event.code` keeps the shortcut independent of the
* keyboard layout, and requiring Ctrl/Meta makes it safe to fire
* while typing in the editor.
*/
export const usePresenterToggleShortcut = (
onToggle: () => void,
enabled = true,
) => {
useEffect(() => {
if (!enabled) {
return;
}

const handleKeyDown = (event: KeyboardEvent) => {
if (
event.code === 'KeyP' &&
(event.ctrlKey || event.metaKey) &&
event.altKey &&
!event.shiftKey &&
!event.repeat
Comment on lines +19 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against AltGraph false positives in the shortcut predicate.

On international layouts, AltGr commonly sets both ctrlKey and altKey; at Line 21–22 this can unintentionally satisfy Mod+Alt+P and toggle presenter while typing. Exclude AltGraph explicitly.

Suggested patch
       if (
         event.code === 'KeyP' &&
         (event.ctrlKey || event.metaKey) &&
         event.altKey &&
+        !event.getModifierState('AltGraph') &&
         !event.shiftKey &&
         !event.repeat
       ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
event.code === 'KeyP' &&
(event.ctrlKey || event.metaKey) &&
event.altKey &&
!event.shiftKey &&
!event.repeat
if (
event.code === 'KeyP' &&
(event.ctrlKey || event.metaKey) &&
event.altKey &&
!event.getModifierState('AltGraph') &&
!event.shiftKey &&
!event.repeat
) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/frontend/apps/impress/src/features/docs/doc-presenter/hooks/usePresenterToggleShortcut.ts`
around lines 19 - 24, The shortcut predicate in usePresenterToggleShortcut's
keydown handler currently treats simultaneous ctrlKey+altKey as Mod+Alt and can
be triggered by AltGraph on international keyboards; update the predicate to
explicitly exclude AltGraph by checking event.getModifierState('AltGraph')
(i.e., require !event.getModifierState('AltGraph')) alongside the existing
checks for event.ctrlKey/event.metaKey and event.altKey so AltGr won't produce a
false positive when pressing Mod+Alt+P.

) {
event.preventDefault();
onToggle();
}
};

window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [onToggle, enabled]);
};