mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-05 00:14:18 +00:00
fix: end Files and composer menu entries that open a dialog with …, and translate Share
The Files tests now look for the new menuAudit keys: the menu entries moved to them so they can end in "…" while toolbar buttons keep the plain label.
This commit is contained in:
@@ -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', () => {
|
||||
|
||||
@@ -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({
|
||||
|
||||
<ContextMenu ref={sendMenu.menuRef} isOpen={showSendMenu} position={sendMenu.contextMenu.position} onClose={closeSendMenu}>
|
||||
{composerClient?.hasDelayedSend() && (
|
||||
<ContextMenuItem icon={CalendarClock} label={t('schedule_send')} onClick={openScheduleDialog} testId="composer-schedule-send" />
|
||||
<ContextMenuItem icon={CalendarClock} label={tMenu('schedule_send')} shortcut={formatShortcutCombo('Mod + Shift + Enter')} onClick={openScheduleDialog} testId="composer-schedule-send" />
|
||||
)}
|
||||
<ComposerPluginMenuItems
|
||||
surface="composer-send-menu"
|
||||
|
||||
@@ -221,7 +221,7 @@ describe('FileBrowser', () => {
|
||||
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(<Probe />);
|
||||
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');
|
||||
|
||||
@@ -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)) && <ContextMenuSeparator />}
|
||||
{canShare(resource) && (
|
||||
<ContextMenuItem icon={Share2} label={t("share")} onClick={() => { closeMenu(); setShareTargetId(resource.id); }} />
|
||||
<ContextMenuItem icon={Share2} label={tMenu("files_share")} onClick={() => { closeMenu(); setShareTargetId(resource.id); }} />
|
||||
)}
|
||||
{resource.isDirectory && !resource.isShared && (
|
||||
<ContextMenuItem
|
||||
@@ -992,10 +994,10 @@ export function FileBrowser({
|
||||
|
||||
const newItems = (
|
||||
<>
|
||||
<ContextMenuItem icon={Upload} label={t("upload_files")} onClick={() => { closeMenu(); fileInputRef.current?.click(); }} />
|
||||
<ContextMenuItem icon={FolderUp} label={t("upload_folder")} onClick={() => { closeMenu(); folderInputRef.current?.click(); }} />
|
||||
<ContextMenuItem icon={Upload} label={tMenu("files_upload_files")} onClick={() => { closeMenu(); fileInputRef.current?.click(); }} />
|
||||
<ContextMenuItem icon={FolderUp} label={tMenu("files_upload_folder")} onClick={() => { closeMenu(); folderInputRef.current?.click(); }} />
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem icon={FolderPlus} label={t("new_folder")} onClick={() => { closeMenu(); setShowNewFolder(true); }} />
|
||||
<ContextMenuItem icon={FolderPlus} label={tMenu("files_new_folder")} onClick={() => { closeMenu(); setShowNewFolder(true); }} />
|
||||
<ContextMenuItem icon={FilePlus} label={newFileLabel} onClick={() => { 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 },
|
||||
|
||||
@@ -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 && (
|
||||
<Button size="sm" variant="outline" className="h-8 gap-1.5" onClick={onShare}>
|
||||
<Share2 className="w-4 h-4" />{t("share")}
|
||||
<Share2 className="w-4 h-4" />{tMenu("files_share_button")}
|
||||
</Button>
|
||||
)}
|
||||
{resource.isDirectory && !resource.isShared && (
|
||||
|
||||
@@ -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 = <ResourceIcon resource={resource} size="sm" options={iconOptions} />;
|
||||
return (
|
||||
@@ -299,7 +300,7 @@ function FileListRow({ resource, isSelected, isFocused, isTabStop, isDropTarget,
|
||||
<RowAction icon={Download} label={t("download")} onClick={() => handlers.onDownload?.(resource)} />
|
||||
)}
|
||||
{!touch && handlers.canShare(resource) && (
|
||||
<RowAction icon={Share2} label={t("share")} onClick={() => handlers.onShare(resource)} />
|
||||
<RowAction icon={Share2} label={tMenu("files_share_button")} onClick={() => handlers.onShare(resource)} />
|
||||
)}
|
||||
<IconButton
|
||||
data-item-action
|
||||
|
||||
@@ -120,6 +120,7 @@ export function FilesHeader({
|
||||
panelToggle,
|
||||
}: FilesHeaderProps) {
|
||||
const t = useTranslations("files");
|
||||
const tMenu = useTranslations("menuAudit");
|
||||
const sortMenu = useContextMenu<null>();
|
||||
const overflowMenu = useContextMenu<Crumb[]>();
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
@@ -159,7 +160,7 @@ export function FilesHeader({
|
||||
<ToolButton icon={Download} label={t("download")} onClick={selection.onDownload} compact />
|
||||
)}
|
||||
{selection.canShare && (
|
||||
<ToolButton icon={Share2} label={t("share")} onClick={selection.onShare} compact />
|
||||
<ToolButton icon={Share2} label={tMenu("files_share_button")} onClick={selection.onShare} compact />
|
||||
)}
|
||||
{selection.canRename && (
|
||||
<ToolButton icon={Pencil} label={t("rename")} onClick={selection.onRename} compact />
|
||||
|
||||
@@ -125,6 +125,7 @@ export function FilesSidebar({
|
||||
onDropToTrash,
|
||||
}: FilesSidebarProps) {
|
||||
const t = useTranslations("files");
|
||||
const tMenu = useTranslations("menuAudit");
|
||||
const newMenu = useContextMenu<null>();
|
||||
const itemMenu = useContextMenu<MenuTarget>();
|
||||
const newButtonRef = useRef<HTMLButtonElement>(null);
|
||||
@@ -274,10 +275,10 @@ export function FilesSidebar({
|
||||
const createItems = (path: string, id: string | null) => (
|
||||
<>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem icon={FolderPlus} label={t("new_folder")} onClick={() => run(() => onCreateIn(path, id, "folder"))} />
|
||||
<ContextMenuItem icon={FilePlus} label={newFileLabel ?? t("new_text_file")} onClick={() => run(() => onCreateIn(path, id, "text"))} />
|
||||
<ContextMenuItem icon={Upload} label={t("upload_files")} onClick={() => run(() => onCreateIn(path, id, "files"))} />
|
||||
<ContextMenuItem icon={FolderUp} label={t("upload_folder")} onClick={() => run(() => onCreateIn(path, id, "dir"))} />
|
||||
<ContextMenuItem icon={FolderPlus} label={tMenu("files_new_folder")} onClick={() => run(() => onCreateIn(path, id, "folder"))} />
|
||||
<ContextMenuItem icon={FilePlus} label={newFileLabel ?? tMenu("files_new_text_file")} onClick={() => run(() => onCreateIn(path, id, "text"))} />
|
||||
<ContextMenuItem icon={Upload} label={tMenu("files_upload_files")} onClick={() => run(() => onCreateIn(path, id, "files"))} />
|
||||
<ContextMenuItem icon={FolderUp} label={tMenu("files_upload_folder")} onClick={() => run(() => onCreateIn(path, id, "dir"))} />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -455,11 +456,11 @@ export function FilesSidebar({
|
||||
</nav>
|
||||
|
||||
<ContextMenu ref={newMenu.menuRef} isOpen={newMenu.contextMenu.isOpen} position={newMenu.contextMenu.position} onClose={newMenu.closeContextMenu}>
|
||||
<ContextMenuItem icon={Upload} label={t("upload_files")} onClick={() => { newMenu.closeContextMenu(); onUploadFiles(); }} />
|
||||
<ContextMenuItem icon={FolderUp} label={t("upload_folder")} onClick={() => { newMenu.closeContextMenu(); onUploadFolder(); }} />
|
||||
<ContextMenuItem icon={Upload} label={tMenu("files_upload_files")} onClick={() => { newMenu.closeContextMenu(); onUploadFiles(); }} />
|
||||
<ContextMenuItem icon={FolderUp} label={tMenu("files_upload_folder")} onClick={() => { newMenu.closeContextMenu(); onUploadFolder(); }} />
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem icon={FolderPlus} label={t("new_folder")} onClick={() => { newMenu.closeContextMenu(); onNewFolder(); }} />
|
||||
<ContextMenuItem icon={FilePlus} label={newFileLabel ?? t("new_text_file")} onClick={() => { newMenu.closeContextMenu(); onNewTextFile(); }} />
|
||||
<ContextMenuItem icon={FolderPlus} label={tMenu("files_new_folder")} onClick={() => { newMenu.closeContextMenu(); onNewFolder(); }} />
|
||||
<ContextMenuItem icon={FilePlus} label={newFileLabel ?? tMenu("files_new_text_file")} onClick={() => { newMenu.closeContextMenu(); onNewTextFile(); }} />
|
||||
</ContextMenu>
|
||||
|
||||
<ContextMenu ref={itemMenu.menuRef} isOpen={itemMenu.contextMenu.isOpen} position={itemMenu.contextMenu.position} onClose={itemMenu.closeContextMenu}>
|
||||
@@ -484,7 +485,7 @@ export function FilesSidebar({
|
||||
Details act on the list and are left out here (R6). */}
|
||||
{(!folder.isShared || canShare(folder)) && <ContextMenuSeparator />}
|
||||
{canShare(folder) && (
|
||||
<ContextMenuItem icon={Share2} label={t("share")} onClick={() => run(() => onShare(folder))} />
|
||||
<ContextMenuItem icon={Share2} label={tMenu("files_share")} onClick={() => run(() => onShare(folder))} />
|
||||
)}
|
||||
{!folder.isShared && (
|
||||
<ContextMenuItem
|
||||
|
||||
Reference in New Issue
Block a user