Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged.

## Unreleased

### Ctrl+B shows and hides the sidebar on a layout that does not write Latin letters

The sidebar toggle's tooltip names Ctrl+B, or ⌘B on a Mac, and the shortcut was recognised by the
character the key writes. On a Russian or Greek layout the B key writes "и" or "β", so the shortcut
did nothing there. It is now read the way the app's own shortcuts read it since Shift+N was fixed
for the same layouts: from the physical key when the layout writes a character outside ASCII there.
### Scrolling a Bot's browser no longer scrolls or zooms the page around it

While somebody drives a Bot's browser, a turn of the mouse wheel over its screen is sent to it, and
Expand Down
5 changes: 4 additions & 1 deletion app/src/components/ui/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";

import { useIsMobile } from "@/hooks/use-mobile";
import { keyOf } from "@/lib/hotkeys/hotkeys";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
Expand Down Expand Up @@ -112,8 +113,10 @@ function SidebarProvider({
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
// `keyOf` rather than `key`, as the app's own shortcuts read it: on a layout that writes
// another script the B key writes "и" or "β", and only its `code` still says B.
if (
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
keyOf(event) === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
event.preventDefault();
Expand Down
80 changes: 80 additions & 0 deletions app/tests/sidebar-shortcut-layouts.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { afterAll, afterEach, beforeAll, expect, test } from "bun:test";
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import { act, cleanup, render } from "@testing-library/react";
import { SidebarProvider, useSidebar } from "@/components/ui/sidebar";

/**
* The sidebar shortcut on a keyboard layout that does not write Latin letters.
*
* The toggle's tooltip names it as Ctrl+B, or ⌘B on a Mac, and the sidebar's listener compared
* `KeyboardEvent.key` with "b". On Russian the B key writes "и", and on Greek it writes "β", so the
* shortcut the tooltip names never fired for anybody with one of those layouts selected. It is the
* same miss Shift+N and the paste shortcut on a Bot's screen had.
*/

beforeAll(() => {
GlobalRegistrator.register();
});

afterEach(() => {
cleanup();
});

afterAll(() => {
GlobalRegistrator.unregister();
});

function SidebarState() {
return <output>{useSidebar().open ? "open" : "closed"}</output>;
}

type Modifiers = Partial<Pick<KeyboardEvent, "ctrlKey" | "metaKey">>;

/**
* A sidebar, and a way to press a key at it that answers with the state it is left in.
*
* The keydown is the one a browser reports: `key` from the layout, `code` from the physical key.
*/
function renderSidebar() {
const { container } = render(
<SidebarProvider>
<SidebarState />
</SidebarProvider>,
);
return (key: string, code: string, modifiers: Modifiers) => {
act(() => {
window.dispatchEvent(
new KeyboardEvent("keydown", {
key,
code,
...modifiers,
bubbles: true,
cancelable: true,
}),
);
});
return container.querySelector("output")?.textContent;
};
}

test("Ctrl+B and Cmd+B toggle the sidebar on a layout that writes another script", () => {
const press = renderSidebar();

expect(press("и", "KeyB", { ctrlKey: true })).toBe("closed");
expect(press("β", "KeyB", { metaKey: true })).toBe("open");
});

test("a layout that writes Latin letters still goes by the letter, wherever its key is", () => {
const press = renderSidebar();

// Dvorak writes B on the key QWERTY calls N, and X on the key QWERTY calls B.
expect(press("b", "KeyN", { ctrlKey: true })).toBe("closed");
expect(press("x", "KeyB", { ctrlKey: true })).toBe("closed");
});

test("another letter, or the B key without its modifier, still does nothing", () => {
const press = renderSidebar();

expect(press("т", "KeyN", { ctrlKey: true })).toBe("open");
expect(press("и", "KeyB", {})).toBe("open");
});
Loading