diff --git a/components/email/__tests__/composer-plugin-surfaces.test.tsx b/components/email/__tests__/composer-plugin-surfaces.test.tsx index 787389c18..082e40541 100644 --- a/components/email/__tests__/composer-plugin-surfaces.test.tsx +++ b/components/email/__tests__/composer-plugin-surfaces.test.tsx @@ -327,6 +327,8 @@ describe('composer-send-menu', () => { const schedule = screen.getAllByRole('menuitem', { name: /schedule_send/ })[0]; const plugin = screen.getAllByRole('menuitem', { name: 'Send with Later' })[0]; expect(follows(schedule, plugin)).toBe(true); + // It opens a dialog, and shows the key that opens it from the composer. + expect(schedule).toHaveTextContent('Ctrl + Shift + Enter'); }); it('keeps a plain Send button without plugin entries or scheduled send', () => { diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index 3409f77db..bf3a3528c 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -56,6 +56,7 @@ import { debug } from "@/lib/debug"; import { toast } from "@/stores/toast-store"; import { useContextMenu, isContextMenuKey } from "@/hooks/use-context-menu"; import { ContextMenu, ContextMenuItem, ContextMenuSeparator } from "@/components/ui/context-menu"; +import { formatShortcutCombo } from "@/lib/shortcut-keys"; import { sanitizeSignatureHtml, sanitizeSignatureHtmlForDisplay, sanitizeEmailHtml, escapeHtml, sanitizePluginBodyHtml } from "@/lib/email-sanitization"; import { buildReplySubject, buildForwardSubject } from "@/lib/subject-prefix"; import { isFilePreviewable } from "@/lib/file-preview"; @@ -592,6 +593,7 @@ export function EmailComposer({ replyTo }: EmailComposerProps) { const t = useTranslations('email_composer'); + const tMenu = useTranslations('menuAudit'); const tCommon = useTranslations('common'); const tWindows = useTranslations('pwaWindows'); const tQuote = useTranslations('quote_header'); @@ -5813,7 +5815,7 @@ export function EmailComposer({ {composerClient?.hasDelayedSend() && ( - + )} { renderBrowser({ resources: [resource(OWN[1])], allFolders: [shared] }); fireEvent.contextMenu(within(tree()).getByText('Documents')); - fireEvent.click(await screen.findByRole('menuitem', { name: 'share' })); + fireEvent.click(await screen.findByRole('menuitem', { name: 'files_share' })); expect(await screen.findByTestId('share-dialog')).toHaveTextContent('Documents:p1'); }); @@ -230,7 +230,7 @@ describe('FileBrowser', () => { renderBrowser(); await screen.findAllByText('Team'); fireEvent.contextMenu(within(tree()).getByText('Team')); - expect(screen.queryByRole('menuitem', { name: 'share' })).toBeNull(); + expect(screen.queryByRole('menuitem', { name: 'files_share' })).toBeNull(); }); it('hides dot files unless the setting shows them', () => { @@ -284,7 +284,7 @@ describe('FileBrowser', () => { it('offers new and upload entries on My files in the sidebar', () => { renderBrowser(); fireEvent.contextMenu(within(tree()).getByText('my_files')); - for (const name of ['open', 'new_folder', 'new_text_file', 'upload_files', 'upload_folder']) { + for (const name of ['open', 'files_new_folder', 'files_new_text_file', 'files_upload_files', 'files_upload_folder']) { expect(screen.getByRole('menuitem', { name })).toBeInTheDocument(); } }); @@ -457,7 +457,7 @@ describe('FileBrowser', () => { } render(); const files = groups.find((g) => g.label === 'files'); - expect(files?.commands.map((c) => c.label)).toEqual(expect.arrayContaining(['upload_files', 'new_folder', 'trash'])); + expect(files?.commands.map((c) => c.label)).toEqual(expect.arrayContaining(['files_upload_files', 'files_new_folder', 'trash'])); fireEvent.keyDown(document.body, { key: '/' }); expect(document.activeElement).toHaveAttribute('role', 'searchbox'); diff --git a/components/files/file-browser.tsx b/components/files/file-browser.tsx index 5338bf98b..8d7c6ca00 100644 --- a/components/files/file-browser.tsx +++ b/components/files/file-browser.tsx @@ -222,7 +222,9 @@ export function FileBrowser({ onShare, }: FileBrowserProps) { const t = useTranslations("files"); - const newFileLabel = newFileFormats.length > 0 ? t("new_file") : t("new_text_file"); + const tMenu = useTranslations("menuAudit"); + // Menu entries that open a dialog end in "…". + const newFileLabel = newFileFormats.length > 0 ? tMenu("files_new_file") : tMenu("files_new_text_file"); const fileNameRules = useMemo(() => client?.getFileNameRules?.() ?? null, [client]); const validateFileName = useCallback((name: string) => { const problem = fileNameProblem(name, fileNameRules); @@ -951,7 +953,7 @@ export function FileBrowser({ )} {(canShare(resource) || (resource.isDirectory && !resource.isShared)) && } {canShare(resource) && ( - { closeMenu(); setShareTargetId(resource.id); }} /> + { closeMenu(); setShareTargetId(resource.id); }} /> )} {resource.isDirectory && !resource.isShared && ( - { closeMenu(); fileInputRef.current?.click(); }} /> - { closeMenu(); folderInputRef.current?.click(); }} /> + { closeMenu(); fileInputRef.current?.click(); }} /> + { closeMenu(); folderInputRef.current?.click(); }} /> - { closeMenu(); setShowNewFolder(true); }} /> + { closeMenu(); setShowNewFolder(true); }} /> { closeMenu(); setShowNewTextFile(true); }} /> ); @@ -1035,9 +1037,9 @@ export function FileBrowser({ const tShell = useTranslations("shell"); const isEmbedded = useIsEmbedded(); useRegisterCommands(tSidebar("files"), [ - { id: "upload-files", label: t("upload_files"), icon: Upload, run: () => fileInputRef.current?.click(), when: () => canCreate }, - { id: "upload-folder", label: t("upload_folder"), icon: FolderUp, run: () => folderInputRef.current?.click(), when: () => canCreate }, - { id: "new-folder", label: t("new_folder"), icon: FolderPlus, run: () => setShowNewFolder(true), when: () => canCreate }, + { id: "upload-files", label: tMenu("files_upload_files"), icon: Upload, run: () => fileInputRef.current?.click(), when: () => canCreate }, + { id: "upload-folder", label: tMenu("files_upload_folder"), icon: FolderUp, run: () => folderInputRef.current?.click(), when: () => canCreate }, + { id: "new-folder", label: tMenu("files_new_folder"), icon: FolderPlus, run: () => setShowNewFolder(true), when: () => canCreate }, { id: "new-text-file", label: newFileLabel, icon: FilePlus, run: () => setShowNewTextFile(true), when: () => canCreate }, { id: "trash", label: t("trash"), icon: Trash2, run: () => onNavigate(TRASH_PATH, TRASH_LOCATION), when: () => !inTrash && !accountPickerMode }, { id: "empty-trash", label: t("empty_trash"), icon: Trash2, run: () => onEmptyTrash?.(), when: () => inTrash && trashCount > 0 && !!onEmptyTrash }, diff --git a/components/files/file-details-panel.tsx b/components/files/file-details-panel.tsx index 34facf37d..57c2e135d 100644 --- a/components/files/file-details-panel.tsx +++ b/components/files/file-details-panel.tsx @@ -56,6 +56,7 @@ export function FileDetailsPanel({ iconOptions, getImageUrl, onClose, onOpen, onEdit, onDownload, onShare, onToggleStar, className, pluginFile, }: FileDetailsPanelProps) { const t = useTranslations("files"); + const tMenu = useTranslations("menuAudit"); const pluginProps = useMemo(() => (pluginFile ? { file: pluginFile } : null), [pluginFile]); const header = ( @@ -147,7 +148,7 @@ export function FileDetailsPanel({ )} {canShare && ( )} {resource.isDirectory && !resource.isShared && ( diff --git a/components/files/file-views.tsx b/components/files/file-views.tsx index 11a26656c..972e8cfad 100644 --- a/components/files/file-views.tsx +++ b/components/files/file-views.tsx @@ -223,6 +223,7 @@ function FileListRow({ resource, isSelected, isFocused, isTabStop, isDropTarget, note?: string; }) { const t = useTranslations("files"); + const tMenu = useTranslations("menuAudit"); const touchProps = useTouchPress(touch, selecting, resource, handlers); const icon = ; return ( @@ -299,7 +300,7 @@ function FileListRow({ resource, isSelected, isFocused, isTabStop, isDropTarget, handlers.onDownload?.(resource)} /> )} {!touch && handlers.canShare(resource) && ( - handlers.onShare(resource)} /> + handlers.onShare(resource)} /> )} (); const overflowMenu = useContextMenu(); const [searchOpen, setSearchOpen] = useState(false); @@ -159,7 +160,7 @@ export function FilesHeader({ )} {selection.canShare && ( - + )} {selection.canRename && ( diff --git a/components/files/files-sidebar.tsx b/components/files/files-sidebar.tsx index 58dc0b629..d47ea17a5 100644 --- a/components/files/files-sidebar.tsx +++ b/components/files/files-sidebar.tsx @@ -125,6 +125,7 @@ export function FilesSidebar({ onDropToTrash, }: FilesSidebarProps) { const t = useTranslations("files"); + const tMenu = useTranslations("menuAudit"); const newMenu = useContextMenu(); const itemMenu = useContextMenu(); const newButtonRef = useRef(null); @@ -274,10 +275,10 @@ export function FilesSidebar({ const createItems = (path: string, id: string | null) => ( <> - run(() => onCreateIn(path, id, "folder"))} /> - run(() => onCreateIn(path, id, "text"))} /> - run(() => onCreateIn(path, id, "files"))} /> - run(() => onCreateIn(path, id, "dir"))} /> + run(() => onCreateIn(path, id, "folder"))} /> + run(() => onCreateIn(path, id, "text"))} /> + run(() => onCreateIn(path, id, "files"))} /> + run(() => onCreateIn(path, id, "dir"))} /> ); @@ -455,11 +456,11 @@ export function FilesSidebar({ - { newMenu.closeContextMenu(); onUploadFiles(); }} /> - { newMenu.closeContextMenu(); onUploadFolder(); }} /> + { newMenu.closeContextMenu(); onUploadFiles(); }} /> + { newMenu.closeContextMenu(); onUploadFolder(); }} /> - { newMenu.closeContextMenu(); onNewFolder(); }} /> - { newMenu.closeContextMenu(); onNewTextFile(); }} /> + { newMenu.closeContextMenu(); onNewFolder(); }} /> + { newMenu.closeContextMenu(); onNewTextFile(); }} /> @@ -484,7 +485,7 @@ export function FilesSidebar({ Details act on the list and are left out here (R6). */} {(!folder.isShared || canShare(folder)) && } {canShare(folder) && ( - run(() => onShare(folder))} /> + run(() => onShare(folder))} /> )} {!folder.isShared && (