From 1230e1b30cbc5b21d8a24d11196a1897ff2442c8 Mon Sep 17 00:00:00 2001 From: pedr Date: Fri, 7 Feb 2025 17:41:22 -0300 Subject: [PATCH] Desktop: Accessibility: Add a new shortcut to set focus to editor toolbar (#11764) --- .eslintignore | 1 + .gitignore | 1 + packages/app-desktop/commands/focusElement.ts | 1 + packages/app-desktop/gui/MenuBar.tsx | 1 + .../NoteBody/CodeMirror/Toolbar.tsx | 1 + .../commands/focusElementToolbar.ts | 36 +++++++++++++++++++ .../gui/NoteEditor/commands/index.ts | 2 ++ .../app-desktop/gui/NoteEditor/utils/types.ts | 10 ++++++ .../utils/useWindowCommandHandler.ts | 11 +++--- packages/app-desktop/gui/ToolbarBase.tsx | 2 ++ packages/app-desktop/gui/menuCommandNames.ts | 1 + packages/lib/services/KeymapService.ts | 2 ++ 12 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 packages/app-desktop/gui/NoteEditor/commands/focusElementToolbar.ts diff --git a/.eslintignore b/.eslintignore index ea3d029142..46dc6fde3b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -272,6 +272,7 @@ packages/app-desktop/gui/NoteEditor/WarningBanner/BannerContent.js packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js +packages/app-desktop/gui/NoteEditor/commands/focusElementToolbar.js packages/app-desktop/gui/NoteEditor/commands/index.js packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js diff --git a/.gitignore b/.gitignore index 05d11f2dff..0928ec8c49 100644 --- a/.gitignore +++ b/.gitignore @@ -247,6 +247,7 @@ packages/app-desktop/gui/NoteEditor/WarningBanner/BannerContent.js packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js +packages/app-desktop/gui/NoteEditor/commands/focusElementToolbar.js packages/app-desktop/gui/NoteEditor/commands/index.js packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.js diff --git a/packages/app-desktop/commands/focusElement.ts b/packages/app-desktop/commands/focusElement.ts index 3840d0648f..904e4d51f9 100644 --- a/packages/app-desktop/commands/focusElement.ts +++ b/packages/app-desktop/commands/focusElement.ts @@ -16,6 +16,7 @@ export const runtime = (): CommandRuntime => { if (target === 'noteList') return CommandService.instance().execute('focusElementNoteList'); if (target === 'sideBar') return CommandService.instance().execute('focusElementSideBar'); if (target === 'noteTitle') return CommandService.instance().execute('focusElementNoteTitle', options); + if (target === 'toolbar') return CommandService.instance().execute('focusElementToolbar', options); throw new Error(`Invalid focus target: ${target}`); }, }; diff --git a/packages/app-desktop/gui/MenuBar.tsx b/packages/app-desktop/gui/MenuBar.tsx index 9d58e87803..26884bd340 100644 --- a/packages/app-desktop/gui/MenuBar.tsx +++ b/packages/app-desktop/gui/MenuBar.tsx @@ -479,6 +479,7 @@ function useMenu(props: Props) { menuItemDic.focusElementNoteList, menuItemDic.focusElementNoteTitle, menuItemDic.focusElementNoteBody, + menuItemDic.focusElementToolbar, ]; const importItems = []; diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.tsx b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.tsx index 38c2aa2174..b54f44317d 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.tsx +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.tsx @@ -32,6 +32,7 @@ function Toolbar(props: ToolbarProps) { const styles = styles_(props); return ( _('Toolbar'), + parentLabel: () => _('Focus'), +}; + +export const runtime = (dependencies: WindowCommandDependencies): CommandRuntime => { + return { + execute: async () => { + if (!dependencies || !dependencies.containerRef || !dependencies.containerRef.current) return; + + const firstButtonOnRTEToolbar = dependencies.containerRef.current.querySelector( + '.tox-toolbar__group button', + ); + + if (firstButtonOnRTEToolbar) { + focus('focusElementToolbar', firstButtonOnRTEToolbar); + return; + } + + const firstButtonOnMarkdownToolbar = dependencies.containerRef.current.querySelector( + '#CodeMirrorToolbar .button:not(.disabled)', + ); + + if (firstButtonOnMarkdownToolbar) { + focus('focusElementToolbar', firstButtonOnMarkdownToolbar); + } + + }, + }; +}; diff --git a/packages/app-desktop/gui/NoteEditor/commands/index.ts b/packages/app-desktop/gui/NoteEditor/commands/index.ts index 5e4c02028e..7871d04462 100644 --- a/packages/app-desktop/gui/NoteEditor/commands/index.ts +++ b/packages/app-desktop/gui/NoteEditor/commands/index.ts @@ -1,6 +1,7 @@ // AUTO-GENERATED using `gulp buildScriptIndexes` import * as focusElementNoteBody from './focusElementNoteBody'; import * as focusElementNoteTitle from './focusElementNoteTitle'; +import * as focusElementToolbar from './focusElementToolbar'; import * as pasteAsText from './pasteAsText'; import * as showLocalSearch from './showLocalSearch'; import * as showRevisions from './showRevisions'; @@ -8,6 +9,7 @@ import * as showRevisions from './showRevisions'; const index: any[] = [ focusElementNoteBody, focusElementNoteTitle, + focusElementToolbar, pasteAsText, showLocalSearch, showRevisions, diff --git a/packages/app-desktop/gui/NoteEditor/utils/types.ts b/packages/app-desktop/gui/NoteEditor/utils/types.ts index ccd9cb684c..e0b74acf8f 100644 --- a/packages/app-desktop/gui/NoteEditor/utils/types.ts +++ b/packages/app-desktop/gui/NoteEditor/utils/types.ts @@ -11,6 +11,8 @@ import { ParseOptions } from '@joplin/lib/HtmlToMd'; import { ScrollStrategy } from '@joplin/editor/CodeMirror/CodeMirrorControl'; import { MarkupToHtmlOptions } from '../../hooks/useMarkupToHtml'; import { ScrollbarSize } from '@joplin/lib/models/settings/builtInMetadata'; +import { RefObject, SetStateAction } from 'react'; +import * as React from 'react'; export interface AllAssetsOptions { contentMaxWidthTarget?: string; @@ -272,3 +274,11 @@ export interface ScrollToTextValue { element: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'strong' | 'ul'; scrollStrategy?: ScrollStrategy; } + +export interface WindowCommandDependencies { + setShowLocalSearch: React.Dispatch>; + noteSearchBarRef: RefObject; + editorRef: RefObject; + titleInputRef: RefObject; + containerRef: RefObject; +} diff --git a/packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.ts b/packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.ts index 595d51e0c4..30621c6a4d 100644 --- a/packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.ts +++ b/packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.ts @@ -1,5 +1,5 @@ -import { RefObject, useEffect } from 'react'; -import { NoteBodyEditorRef, OnChangeEvent, ScrollOptionTypes } from './types'; +import { RefObject, Dispatch, SetStateAction, useEffect } from 'react'; +import { WindowCommandDependencies, NoteBodyEditorRef, OnChangeEvent, ScrollOptionTypes } from './types'; import editorCommandDeclarations, { enabledCondition } from '../editorCommandDeclarations'; import CommandService, { CommandDeclaration, CommandRuntime, CommandContext, RegisteredRuntime } from '@joplin/lib/services/CommandService'; import time from '@joplin/lib/time'; @@ -10,14 +10,14 @@ const commandsWithDependencies = [ require('../commands/showLocalSearch'), require('../commands/focusElementNoteTitle'), require('../commands/focusElementNoteBody'), + require('../commands/focusElementToolbar'), require('../commands/pasteAsText'), ]; type OnBodyChange = (event: OnChangeEvent)=> void; interface HookDependencies { - // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied - setShowLocalSearch: Function; + setShowLocalSearch: Dispatch>; // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied dispatch: Function; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied @@ -93,11 +93,12 @@ export default function useWindowCommandHandler(dependencies: HookDependencies) )); } - const dependencies = { + const dependencies: WindowCommandDependencies = { editorRef, setShowLocalSearch, noteSearchBarRef, titleInputRef, + containerRef, }; for (const command of commandsWithDependencies) { diff --git a/packages/app-desktop/gui/ToolbarBase.tsx b/packages/app-desktop/gui/ToolbarBase.tsx index 8c614974de..2628cd909d 100644 --- a/packages/app-desktop/gui/ToolbarBase.tsx +++ b/packages/app-desktop/gui/ToolbarBase.tsx @@ -15,6 +15,7 @@ interface Props { items: ToolbarItem[]; disabled: boolean; 'aria-label': string; + id?: string; } const getItemType = (item: ToolbarItem) => { @@ -181,6 +182,7 @@ const ToolbarBaseComponent: React.FC = props => { className={`editor-toolbar ${props.scrollable ? '-scrollable' : ''}`} style={props.style} + id={props.id ?? undefined} role='toolbar' aria-label={props['aria-label']} diff --git a/packages/app-desktop/gui/menuCommandNames.ts b/packages/app-desktop/gui/menuCommandNames.ts index db7e5c0fc9..4489ae94dd 100644 --- a/packages/app-desktop/gui/menuCommandNames.ts +++ b/packages/app-desktop/gui/menuCommandNames.ts @@ -7,6 +7,7 @@ export default function() { 'focusElementNoteList', 'focusElementNoteTitle', 'focusElementSideBar', + 'focusElementToolbar', 'focusSearch', 'historyBackward', 'historyForward', diff --git a/packages/lib/services/KeymapService.ts b/packages/lib/services/KeymapService.ts index 6980ba6ece..220863561d 100644 --- a/packages/lib/services/KeymapService.ts +++ b/packages/lib/services/KeymapService.ts @@ -38,6 +38,7 @@ const defaultKeymapItems = { { accelerator: 'Shift+Cmd+L', command: 'focusElementNoteList' }, { accelerator: 'Shift+Cmd+N', command: 'focusElementNoteTitle' }, { accelerator: 'Shift+Cmd+B', command: 'focusElementNoteBody' }, + { accelerator: 'Shift+Cmd+O', command: 'focusElementToolbar' }, { accelerator: 'Option+Cmd+S', command: 'toggleSideBar' }, { accelerator: 'Option+Cmd+L', command: 'toggleNoteList' }, { accelerator: 'Cmd+L', command: 'toggleVisiblePanes' }, @@ -86,6 +87,7 @@ const defaultKeymapItems = { { accelerator: 'Ctrl+Shift+L', command: 'focusElementNoteList' }, { accelerator: 'Ctrl+Shift+N', command: 'focusElementNoteTitle' }, { accelerator: 'Ctrl+Shift+B', command: 'focusElementNoteBody' }, + { accelerator: 'Ctrl+Shift+O', command: 'focusElementToolbar' }, { accelerator: 'F10', command: 'toggleSideBar' }, { accelerator: 'Ctrl+Shift+M', command: 'toggleMenuBar' }, { accelerator: 'F11', command: 'toggleNoteList' },