diff --git a/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue b/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue index 2acb07fa85..6d2331e002 100644 --- a/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue +++ b/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue @@ -32,6 +32,8 @@ import { useTablesStore, useTabs, useToggle, + useMagicKeys, + navigateToBlankTargetOpenOption, } from '#imports' import type { NcProject } from '#imports' @@ -71,6 +73,8 @@ const { orgRoles, isUIAllowed } = useRoles() useTabs() +const { meta: metaKey, ctrlKey } = useMagicKeys() + const editMode = ref(false) const tempTitle = ref('') @@ -253,12 +257,29 @@ const onProjectClick = async (base: NcProject, ignoreNavigation?: boolean, toggl if (!base) { return } + const cmdOrCtrl = isMac() ? metaKey.value : ctrlKey.value - if (!toggleIsExpanded) $e('c:base:open') + if (!toggleIsExpanded && !cmdOrCtrl) $e('c:base:open') ignoreNavigation = isMobileMode.value || ignoreNavigation toggleIsExpanded = isMobileMode.value || toggleIsExpanded + if (cmdOrCtrl && !ignoreNavigation) { + await navigateTo( + `${cmdOrCtrl ? '#' : ''}${baseUrl({ + id: base.id!, + type: 'database', + isSharedBase: isSharedBase.value, + })}`, + cmdOrCtrl + ? { + open: navigateToBlankTargetOpenOption, + } + : undefined, + ) + return + } + if (toggleIsExpanded) { base.isExpanded = !base.isExpanded } else { diff --git a/packages/nc-gui/components/dashboard/TreeView/TableNode.vue b/packages/nc-gui/components/dashboard/TreeView/TableNode.vue index 309446907d..e108628e1e 100644 --- a/packages/nc-gui/components/dashboard/TreeView/TableNode.vue +++ b/packages/nc-gui/components/dashboard/TreeView/TableNode.vue @@ -4,7 +4,7 @@ import { toRef } from '@vue/reactivity' import { message } from 'ant-design-vue' import { storeToRefs } from 'pinia' -import { ProjectRoleInj, TreeViewInj, useNuxtApp, useRoles, useTabs } from '#imports' +import { ProjectRoleInj, TreeViewInj, useNuxtApp, useRoles, useTabs, useMagicKeys } from '#imports' import type { SidebarTableNode } from '~/lib' const props = withDefaults( @@ -39,6 +39,8 @@ useTableNew({ baseId: base.value.id!, }) +const { meta: metaKey, ctrlKey } = useMagicKeys() + const baseRole = inject(ProjectRoleInj) provide(SidebarTableInj, table) @@ -106,6 +108,11 @@ const onExpand = async () => { } const onOpenTable = async () => { + if (isMac() ? metaKey.value : ctrlKey.value) { + await _openTable(table.value, true) + return + } + isLoading.value = true try { await _openTable(table.value) diff --git a/packages/nc-gui/composables/useTableNew.ts b/packages/nc-gui/composables/useTableNew.ts index b20494e43b..61ec1ff01b 100644 --- a/packages/nc-gui/composables/useTableNew.ts +++ b/packages/nc-gui/composables/useTableNew.ts @@ -19,6 +19,8 @@ import { useNuxtApp, useTabs, watch, + useMagicKeys, + navigateToBlankTargetOpenOption, } from '#imports' export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => void; baseId: string; sourceId?: string }) { @@ -60,7 +62,7 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v const tables = computed(() => baseTables.value.get(param.baseId) || []) const base = computed(() => bases.value.get(param.baseId)) - const openTable = async (table: SidebarTableNode) => { + const openTable = async (table: SidebarTableNode, cmdOrCtrl: boolean = false) => { if (!table.base_id) return let base = bases.value.get(table.base_id) @@ -86,7 +88,14 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v const navigateToTable = async () => { if (openedViewsTab.value === 'view') { - await navigateTo(`/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}`) + await navigateTo( + `${cmdOrCtrl ? '#' : ''}/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}`, + cmdOrCtrl + ? { + open: navigateToBlankTargetOpenOption, + } + : undefined, + ) } table.isViewsLoading = true @@ -99,7 +108,16 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v // find the default view and navigate to it, if not found navigate to the first one const defaultView = views.find((v) => v.is_default) || views[0] - await navigateTo(`/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}/${defaultView.id}/${openedViewsTab.value}`) + await navigateTo( + `${cmdOrCtrl ? '#' : ''}/${workspaceIdOrType}/${baseIdOrBaseId}/${table?.id}/${defaultView.id}/${ + openedViewsTab.value + }`, + cmdOrCtrl + ? { + open: navigateToBlankTargetOpenOption, + } + : undefined, + ) } } catch (e) { console.error(e) @@ -119,8 +137,11 @@ export function useTableNew(param: { onTableCreate?: (tableMeta: TableType) => v table.isMetaLoading = false } } - - await Promise.all([navigateToTable(), loadTableMeta()]) + if (cmdOrCtrl) { + await navigateToTable() + } else { + await Promise.all([navigateToTable(), loadTableMeta()]) + } } const createTable = async () => { diff --git a/packages/nc-gui/store/views.ts b/packages/nc-gui/store/views.ts index 64e426f48f..5c2a1c9e10 100644 --- a/packages/nc-gui/store/views.ts +++ b/packages/nc-gui/store/views.ts @@ -1,6 +1,7 @@ import type { FilterType, SortType, ViewType, ViewTypes } from 'nocodb-sdk' import { acceptHMRUpdate, defineStore } from 'pinia' import type { ViewPageType } from '~/lib' +import { useMagicKeys, navigateToBlankTargetOpenOption } from '#imports' export const useViewsStore = defineStore('viewsStore', () => { const { $api } = useNuxtApp() @@ -27,6 +28,8 @@ export const useViewsStore = defineStore('viewsStore', () => { const { activeWorkspaceId } = storeToRefs(useWorkspace()) + const { meta: metaKey, ctrlKey } = useMagicKeys() + const recentViews = computed(() => allRecentViews.value.filter((f) => f.workspaceId === activeWorkspaceId.value).splice(0, 10), ) @@ -216,6 +219,8 @@ export const useViewsStore = defineStore('viewsStore', () => { hardReload?: boolean doNotSwitchTab?: boolean }) => { + const cmdOrCtrl = isMac() ? metaKey.value : ctrlKey.value + const routeName = 'index-typeOrId-baseId-index-index-viewId-viewTitle-slugs' let baseIdOrBaseId = baseId @@ -231,29 +236,63 @@ export const useViewsStore = defineStore('viewsStore', () => { router.currentRoute.value.query.page && router.currentRoute.value.query.page === 'fields' ) { - await router.push({ - name: routeName, - params: { - viewTitle: view.id || '', - viewId: tableId, - baseId: baseIdOrBaseId, - slugs, - }, - query: router.currentRoute.value.query, - }) + if (cmdOrCtrl) { + await navigateTo( + router.resolve({ + name: routeName, + params: { + viewTitle: view.id || '', + viewId: tableId, + baseId: baseIdOrBaseId, + slugs, + }, + query: router.currentRoute.value.query, + }).href, + { + open: navigateToBlankTargetOpenOption, + }, + ) + } else { + await router.push({ + name: routeName, + params: { + viewTitle: view.id || '', + viewId: tableId, + baseId: baseIdOrBaseId, + slugs, + }, + query: router.currentRoute.value.query, + }) + } } else { - await router.push({ - name: routeName, - params: { - viewTitle: view.id || '', - viewId: tableId, - baseId: baseIdOrBaseId, - slugs, - }, - }) + if (cmdOrCtrl) { + const href = router.resolve({ + name: routeName, + params: { + viewTitle: view.id || '', + viewId: tableId, + baseId: baseIdOrBaseId, + slugs, + }, + }).href + + await navigateTo(href, { + open: navigateToBlankTargetOpenOption, + }) + } else { + await router.push({ + name: routeName, + params: { + viewTitle: view.id || '', + viewId: tableId, + baseId: baseIdOrBaseId, + slugs, + }, + }) + } } - if (hardReload) { + if (!cmdOrCtrl && hardReload) { await router .replace({ name: routeName, diff --git a/packages/nc-gui/utils/urlUtils.ts b/packages/nc-gui/utils/urlUtils.ts index d0c098b460..b1ab1b826f 100644 --- a/packages/nc-gui/utils/urlUtils.ts +++ b/packages/nc-gui/utils/urlUtils.ts @@ -35,3 +35,11 @@ export const openLink = (path: string, baseURL?: string, target = '_blank') => { const url = new URL(path, baseURL) window.open(url.href, target, 'noopener,noreferrer') } + +export const navigateToBlankTargetOpenOption = { + target: '_blank', + windowFeatures: { + noopener: true, + noreferrer: true, + }, +}