From 139dd17ca033918badefb5ddea7243831981fd8a Mon Sep 17 00:00:00 2001 From: Armand Date: Wed, 23 Sep 2026 13:20:44 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20open=20search=20result?= =?UTF-8?q?s=20in=20a=20new=20tab=20with=20ctrl/cmd+click?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Search results were cmdk items navigating with router.push on select, so the browser had no link to open elsewhere. Each result is now a real link to the document: a plain click still goes through the quick search onSelect, while ctrl, cmd, shift or middle click let the browser open it in a new tab or window and keep the search modal open. Signed-off-by: Armand --- CHANGELOG.md | 1 + .../__tests__/app-impress/doc-search.spec.ts | 46 +++++++++++++++++++ .../doc-search/components/DocSearchItem.tsx | 22 +++++++-- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d62d79b3..8ef9fac21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ and this project adheres to ### Fixed +- 🐛(frontend) open search results in a new tab with ctrl/cmd+click #2719 - 🐛(frontend) clear callout background on Backspace #2052 - 🐛(export) keep image aspect ratio in PDF columns #2670 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts index e76d72d4c..40d963b53 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts @@ -68,6 +68,52 @@ test.describe('Document search', () => { ).toBeHidden(); }); + test('it opens a search result in a new tab with ctrl+click', async ({ + page, + browserName, + }) => { + const [docTitle] = await createDoc( + page, + 'My doc search new tab', + browserName, + 1, + ); + await verifyDocName(page, docTitle); + await page.goto('/'); + await page.getByTestId('search-docs-button').click(); + + const inputSearch = page.getByPlaceholder('Type the name of a document'); + await inputSearch.fill(docTitle); + + const result = page + .getByRole('listbox') + .getByRole('group') + .getByRole('option') + .getByText(docTitle); + await expect(result).toBeVisible(); + + const [newPage] = await Promise.all([ + page.context().waitForEvent('page'), + result.click({ modifiers: ['ControlOrMeta'] }), + ]); + + await verifyDocName(newPage, docTitle); + + // The search stays open in the first tab + await expect( + page.getByRole('heading', { name: 'Search for a document' }), + ).toBeVisible(); + await expect(result).toBeVisible(); + await newPage.close(); + + // A plain click still opens the document in the current tab + await result.click(); + await verifyDocName(page, docTitle); + await expect( + page.getByRole('heading', { name: 'Search for a document' }), + ).toBeHidden(); + }); + test('it checks cmd+k modal search interaction', async ({ page, browserName, diff --git a/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchItem.tsx index 0800aee57..1415a6caf 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchItem.tsx @@ -1,5 +1,7 @@ +import { css } from 'styled-components'; + import ArrowIcon from '@/assets/icons/ui-kit/enter.svg'; -import { Box, Icon } from '@/components'; +import { Box, Icon, StyledLink } from '@/components'; import { QuickSearchItemContent } from '@/components/quick-search/'; import { SimpleDocItem } from '@/docs/doc-management'; import { useResponsiveStore } from '@/stores'; @@ -16,10 +18,22 @@ export const DocSearchItem = ({ doc }: DocSearchItemProps) => { const { filter } = useDocSearchFilterStore(); return ( - { + if (e.metaKey || e.ctrlKey || e.shiftKey) { + e.stopPropagation(); + return; + } + e.preventDefault(); + }} > { /> } /> - + ); };