From f6d28811e502e7f19e440318d0a6f8a0c9a64bf6 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Fri, 4 Jul 2025 09:09:55 -0400 Subject: [PATCH] fix: Cannot ctrl-click to open in a new tab some menu items (#9542) * wip * done --- app/actions/definitions/collections.tsx | 2 +- app/actions/definitions/documents.tsx | 4 +- app/actions/definitions/navigation.tsx | 55 ++++++++++++------- app/actions/definitions/teams.tsx | 5 +- app/actions/index.ts | 25 ++++++++- .../CommandBar/useRecentDocumentActions.tsx | 3 +- .../CommandBar/useSettingsAction.tsx | 3 +- app/components/ContextMenu/MenuItem.tsx | 2 +- app/components/ContextMenu/Template.tsx | 6 +- app/types.ts | 3 +- 10 files changed, 74 insertions(+), 34 deletions(-) diff --git a/app/actions/definitions/collections.tsx b/app/actions/definitions/collections.tsx index 4a2da735ed..9d07ece5d5 100644 --- a/app/actions/definitions/collections.tsx +++ b/app/actions/definitions/collections.tsx @@ -47,7 +47,7 @@ export const openCollection = createAction({ name: collection.name, icon: , section: CollectionSection, - perform: () => history.push(collection.path), + to: collection.path, })); }, }); diff --git a/app/actions/definitions/documents.tsx b/app/actions/definitions/documents.tsx index b7ebc2a0e5..8e766b5040 100644 --- a/app/actions/definitions/documents.tsx +++ b/app/actions/definitions/documents.tsx @@ -98,7 +98,7 @@ export const openDocument = createAction({ ), section: DocumentSection, - perform: () => history.push(item.url), + to: item.url, })); }, }); @@ -840,7 +840,7 @@ export const searchDocumentsForQuery = (query: string) => analyticsName: "Search documents", section: DocumentSection, icon: , - perform: () => history.push(searchPath({ query })), + to: searchPath({ query }), visible: ({ location }) => location.pathname !== searchPath(), }); diff --git a/app/actions/definitions/navigation.tsx b/app/actions/definitions/navigation.tsx index 88463d26ce..1d96cfa2d3 100644 --- a/app/actions/definitions/navigation.tsx +++ b/app/actions/definitions/navigation.tsx @@ -21,7 +21,6 @@ import KeyboardShortcuts from "~/scenes/KeyboardShortcuts"; import { createAction } from "~/actions"; import { NavigationSection, RecentSearchesSection } from "~/actions/sections"; import Desktop from "~/utils/Desktop"; -import history from "~/utils/history"; import isCloudHosted from "~/utils/isCloudHosted"; import { homePath, @@ -38,7 +37,7 @@ export const navigateToHome = createAction({ section: NavigationSection, shortcut: ["d"], icon: , - perform: () => history.push(homePath()), + to: homePath(), visible: ({ location }) => location.pathname !== homePath(), }); @@ -48,7 +47,7 @@ export const navigateToRecentSearchQuery = (searchQuery: SearchQuery) => name: searchQuery.query, analyticsName: "Navigate to recent search query", icon: , - perform: () => history.push(searchPath({ query: searchQuery.query })), + to: searchPath({ query: searchQuery.query }), }); export const navigateToDrafts = createAction({ @@ -56,7 +55,7 @@ export const navigateToDrafts = createAction({ analyticsName: "Navigate to drafts", section: NavigationSection, icon: , - perform: () => history.push(draftsPath()), + to: draftsPath(), visible: ({ location }) => location.pathname !== draftsPath(), }); @@ -65,7 +64,7 @@ export const navigateToSearch = createAction({ analyticsName: "Navigate to search", section: NavigationSection, icon: , - perform: () => history.push(searchPath()), + to: searchPath(), visible: ({ location }) => location.pathname !== searchPath(), }); @@ -75,7 +74,7 @@ export const navigateToArchive = createAction({ section: NavigationSection, shortcut: ["g", "a"], icon: , - perform: () => history.push(archivePath()), + to: archivePath(), visible: ({ location }) => location.pathname !== archivePath(), }); @@ -84,7 +83,7 @@ export const navigateToTrash = createAction({ analyticsName: "Navigate to trash", section: NavigationSection, icon: , - perform: () => history.push(trashPath()), + to: trashPath(), visible: ({ location }) => location.pathname !== trashPath(), }); @@ -95,7 +94,7 @@ export const navigateToSettings = createAction({ shortcut: ["g", "s"], icon: , visible: () => stores.policies.abilities(stores.auth.team?.id || "").update, - perform: () => history.push(settingsPath()), + to: settingsPath(), }); export const navigateToWorkspaceSettings = createAction({ @@ -104,7 +103,7 @@ export const navigateToWorkspaceSettings = createAction({ section: NavigationSection, icon: , visible: () => stores.policies.abilities(stores.auth.team?.id || "").update, - perform: () => history.push(settingsPath("details")), + to: settingsPath("details"), }); export const navigateToProfileSettings = createAction({ @@ -113,7 +112,7 @@ export const navigateToProfileSettings = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => history.push(settingsPath()), + to: settingsPath(), }); export const navigateToTemplateSettings = createAction({ @@ -122,7 +121,7 @@ export const navigateToTemplateSettings = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => history.push(settingsPath("templates")), + to: settingsPath("templates"), }); export const navigateToNotificationSettings = createAction({ @@ -131,7 +130,7 @@ export const navigateToNotificationSettings = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => history.push(settingsPath("notifications")), + to: settingsPath("notifications"), }); export const navigateToAccountPreferences = createAction({ @@ -140,7 +139,7 @@ export const navigateToAccountPreferences = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => history.push(settingsPath("preferences")), + to: settingsPath("preferences"), }); export const openDocumentation = createAction({ @@ -149,7 +148,10 @@ export const openDocumentation = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => window.open(UrlHelper.guide), + to: { + url: UrlHelper.guide, + target: "_blank", + }, }); export const openAPIDocumentation = createAction({ @@ -158,7 +160,10 @@ export const openAPIDocumentation = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => window.open(UrlHelper.developers), + to: { + url: UrlHelper.developers, + target: "_blank", + }, }); export const toggleSidebar = createAction({ @@ -175,14 +180,20 @@ export const openFeedbackUrl = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => window.open(UrlHelper.contact), + to: { + url: UrlHelper.contact, + target: "_blank", + }, }); export const openBugReportUrl = createAction({ name: ({ t }) => t("Report a bug"), analyticsName: "Open bug report", section: NavigationSection, - perform: () => window.open(UrlHelper.github), + to: { + url: UrlHelper.github, + target: "_blank", + }, }); export const openChangelog = createAction({ @@ -191,7 +202,10 @@ export const openChangelog = createAction({ section: NavigationSection, iconInContextMenu: false, icon: , - perform: () => window.open(UrlHelper.changelog), + to: { + url: UrlHelper.changelog, + target: "_blank", + }, }); export const openKeyboardShortcuts = createAction({ @@ -219,8 +233,9 @@ export const downloadApp = createAction({ iconInContextMenu: false, icon: , visible: () => !Desktop.isElectron() && isMac() && isCloudHosted, - perform: () => { - window.open("https://desktop.getoutline.com"); + to: { + url: "https://desktop.getoutline.com", + target: "_blank", }, }); diff --git a/app/actions/definitions/teams.tsx b/app/actions/definitions/teams.tsx index eb4cf85634..fc093070c3 100644 --- a/app/actions/definitions/teams.tsx +++ b/app/actions/definitions/teams.tsx @@ -32,7 +32,10 @@ export const switchTeamsList = ({ stores }: { stores: RootStore }) => ); }, visible: ({ currentTeamId }: ActionContext) => currentTeamId !== session.id, - perform: () => (window.location.href = session.url), + to: { + url: session.url, + target: "_self", + }, })) ?? []; export const switchTeam = createAction({ diff --git a/app/actions/index.ts b/app/actions/index.ts index bf0797bd3a..b901c1ee01 100644 --- a/app/actions/index.ts +++ b/app/actions/index.ts @@ -6,6 +6,8 @@ import { Action, ActionContext, CommandBarAction, + MenuExternalLink, + MenuInternalLink, MenuItemButton, MenuItemWithChildren, } from "~/types"; @@ -31,7 +33,6 @@ export function createAction(definition: Optional): Action { : "contextmenu", }); } - return definition.perform?.(context); } : undefined, @@ -42,7 +43,7 @@ export function createAction(definition: Optional): Action { export function actionToMenuItem( action: Action, context: ActionContext -): MenuItemButton | MenuItemWithChildren { +): MenuItemButton | MenuExternalLink | MenuInternalLink | MenuItemWithChildren { const resolvedIcon = resolve>(action.icon, context); const resolvedChildren = resolve(action.children, context); const visible = action.visible ? action.visible(context) : true; @@ -67,6 +68,26 @@ export function actionToMenuItem( }; } + if (action.to) { + return typeof action.to === "string" + ? { + type: "route", + title, + icon, + visible, + to: action.to, + selected: action.selected?.(context), + } + : { + type: "link", + title, + icon, + visible, + href: action.to, + selected: action.selected?.(context), + }; + } + return { type: "button", title, diff --git a/app/components/CommandBar/useRecentDocumentActions.tsx b/app/components/CommandBar/useRecentDocumentActions.tsx index c19dcc5159..c57bcecb38 100644 --- a/app/components/CommandBar/useRecentDocumentActions.tsx +++ b/app/components/CommandBar/useRecentDocumentActions.tsx @@ -4,7 +4,6 @@ import Icon from "@shared/components/Icon"; import { createAction } from "~/actions"; import { RecentSection } from "~/actions/sections"; import useStores from "~/hooks/useStores"; -import history from "~/utils/history"; import { documentPath } from "~/utils/routeHelpers"; const useRecentDocumentActions = (count = 6) => { @@ -25,7 +24,7 @@ const useRecentDocumentActions = (count = 6) => { ) : ( ), - perform: () => history.push(documentPath(item)), + to: documentPath(item), }) ), [count, ui.activeDocumentId, documents.recentlyViewed] diff --git a/app/components/CommandBar/useSettingsAction.tsx b/app/components/CommandBar/useSettingsAction.tsx index d46234b825..cee5db1472 100644 --- a/app/components/CommandBar/useSettingsAction.tsx +++ b/app/components/CommandBar/useSettingsAction.tsx @@ -3,7 +3,6 @@ import { useMemo } from "react"; import { createAction } from "~/actions"; import { NavigationSection } from "~/actions/sections"; import useSettingsConfig from "~/hooks/useSettingsConfig"; -import history from "~/utils/history"; const useSettingsAction = () => { const config = useSettingsConfig(); @@ -16,7 +15,7 @@ const useSettingsAction = () => { name: item.name, icon: , section: NavigationSection, - perform: () => history.push(item.path), + to: item.path, }; }), [config] diff --git a/app/components/ContextMenu/MenuItem.tsx b/app/components/ContextMenu/MenuItem.tsx index 680dab5502..4ce02b365a 100644 --- a/app/components/ContextMenu/MenuItem.tsx +++ b/app/components/ContextMenu/MenuItem.tsx @@ -20,7 +20,7 @@ type Props = { dangerous?: boolean; to?: LocationDescriptor; href?: string; - target?: "_blank"; + target?: string; as?: string | React.ComponentType; hide?: () => void; level?: number; diff --git a/app/components/ContextMenu/Template.tsx b/app/components/ContextMenu/Template.tsx index aad2de26ae..f565e52771 100644 --- a/app/components/ContextMenu/Template.tsx +++ b/app/components/ContextMenu/Template.tsx @@ -155,12 +155,14 @@ function Template({ items, actions, context, showIcons, ...menu }: Props) { return ( diff --git a/app/types.ts b/app/types.ts index a88e3b62a7..e086d49c58 100644 --- a/app/types.ts +++ b/app/types.ts @@ -65,7 +65,7 @@ export type MenuInternalLink = { export type MenuExternalLink = { type: "link"; title: React.ReactNode; - href: string; + href: string | { url: string; target?: string }; visible?: boolean; selected?: boolean; disabled?: boolean; @@ -117,6 +117,7 @@ export type Action = { * instead. Errors will be caught and displayed to the user as a toast message. */ perform?: (context: ActionContext) => any; + to?: string | { url: string; target?: string }; children?: ((context: ActionContext) => Action[]) | Action[]; };