Skip to content

Commit ab931b4

Browse files
committed
fixup! ✨(frontend) interlinking custom inline content
1 parent d418ddc commit ab931b4

File tree

4 files changed

+100
-19
lines changed

4 files changed

+100
-19
lines changed

src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -731,25 +731,35 @@ test.describe('Doc Editor', () => {
731731
await page.locator('.bn-block-outer').last().fill('/');
732732
await page.getByText('Link to a doc').first().click();
733733

734-
await page
735-
.locator(
736-
"span[data-inline-content-type='interlinkingSearchInline'] input",
737-
)
738-
.fill('interlink-child-1');
734+
const input = page.locator(
735+
"span[data-inline-content-type='interlinkingSearchInline'] input",
736+
);
737+
const searchContainer = page.locator('.quick-search-container');
739738

740-
await page
741-
.locator('.quick-search-container')
742-
.getByText('interlink-child-1')
743-
.click();
739+
await input.fill('doc-interlink');
740+
741+
await expect(searchContainer.getByText(randomDoc)).toBeVisible();
742+
await expect(searchContainer.getByText(docChild1)).toBeVisible();
743+
await expect(searchContainer.getByText(docChild2)).toBeVisible();
744+
745+
await input.pressSequentially('-child');
746+
747+
await expect(searchContainer.getByText(docChild1)).toBeVisible();
748+
await expect(searchContainer.getByText(docChild2)).toBeVisible();
749+
await expect(searchContainer.getByText(randomDoc)).toBeHidden();
750+
751+
// use keydown to select the second result
752+
await page.keyboard.press('ArrowDown');
753+
await page.keyboard.press('Enter');
744754

745755
const interlink = page.getByRole('link', {
746-
name: 'child-1',
756+
name: 'child-2',
747757
});
748758

749759
await expect(interlink).toBeVisible();
750760
await interlink.click();
751761

752-
await verifyDocName(page, docChild1);
762+
await verifyDocName(page, docChild2);
753763
});
754764

755765
test('it checks interlink shortcut @', async ({ page, browserName }) => {

src/frontend/apps/impress/src/components/quick-search/QuickSearch.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Command } from 'cmdk';
2-
import { ReactNode, useRef } from 'react';
2+
import { ReactNode, useEffect, useRef, useState } from 'react';
33

44
import { hasChildrens } from '@/utils/children';
55

@@ -44,12 +44,45 @@ export const QuickSearch = ({
4444
children,
4545
}: QuickSearchProps) => {
4646
const ref = useRef<HTMLDivElement | null>(null);
47+
const [selectedValue, setSelectedValue] = useState<string>('');
48+
49+
// Auto-select first item when children change
50+
useEffect(() => {
51+
if (!children) {
52+
setSelectedValue('');
53+
return;
54+
}
55+
56+
// Small delay for DOM to update
57+
const timeoutId = setTimeout(() => {
58+
const firstItem = ref.current?.querySelector('[cmdk-item]');
59+
if (firstItem) {
60+
const value =
61+
firstItem.getAttribute('data-value') ||
62+
firstItem.getAttribute('value') ||
63+
firstItem.textContent?.trim() ||
64+
'';
65+
if (value) {
66+
setSelectedValue(value);
67+
}
68+
}
69+
}, 50);
70+
71+
return () => clearTimeout(timeoutId);
72+
}, [children]);
4773

4874
return (
4975
<>
5076
<QuickSearchStyle />
5177
<div className="quick-search-container">
52-
<Command label={label} shouldFilter={false} ref={ref}>
78+
<Command
79+
label={label}
80+
shouldFilter={false}
81+
ref={ref}
82+
value={selectedValue}
83+
onValueChange={setSelectedValue}
84+
tabIndex={0}
85+
>
5386
{showInput && (
5487
<QuickSearchInput
5588
loading={loading}

src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ export const SearchPage = ({
123123
setSearch(value);
124124
}}
125125
onKeyDown={(e) => {
126-
if (e.key === 'Backspace' && search.length === 0) {
126+
if (
127+
(e.key === 'Backspace' && search.length === 0) ||
128+
e.key === 'Escape'
129+
) {
127130
e.preventDefault();
128131

129132
updateInlineContent({
@@ -137,6 +140,30 @@ export const SearchPage = ({
137140
contentRef(null);
138141
editor.focus();
139142
editor.insertInlineContent(['']);
143+
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
144+
// Allow arrow keys to be handled by the command menu for navigation
145+
const commandList = e.currentTarget
146+
.closest('.inline-content')
147+
?.nextElementSibling?.querySelector('[cmdk-list]');
148+
149+
// Create a synthetic keyboard event for the command menu
150+
const syntheticEvent = new KeyboardEvent('keydown', {
151+
key: e.key,
152+
bubbles: true,
153+
cancelable: true,
154+
});
155+
commandList?.dispatchEvent(syntheticEvent);
156+
e.preventDefault();
157+
} else if (e.key === 'Enter') {
158+
// Handle Enter key to select the currently highlighted item
159+
const selectedItem = e.currentTarget
160+
.closest('.inline-content')
161+
?.nextElementSibling?.querySelector(
162+
'[cmdk-item][data-selected="true"]',
163+
) as HTMLElement;
164+
165+
selectedItem?.click();
166+
e.preventDefault();
140167
}
141168
}}
142169
/>
@@ -204,8 +231,9 @@ export const SearchPage = ({
204231
title: doc.title || untitledDocument,
205232
},
206233
},
207-
' ',
208234
]);
235+
236+
editor.focus();
209237
}}
210238
renderElement={(doc) => (
211239
<QuickSearchItemContent

src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchSubPageContent.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
22
import { t } from 'i18next';
3-
import React, { useEffect, useMemo } from 'react';
3+
import React, { useEffect, useState } from 'react';
44
import { InView } from 'react-intersection-observer';
55

66
import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search';
@@ -43,10 +43,19 @@ export const DocSearchSubPageContent = ({
4343
enabled: !!treeContext?.root?.id,
4444
},
4545
);
46+
const [docsData, setDocsData] = useState<QuickSearchData<Doc>>({
47+
groupName: '',
48+
elements: [],
49+
emptyString: '',
50+
});
4651

4752
const loading = isFetching || isRefetching || isLoading;
4853

49-
const docsData: QuickSearchData<Doc> = useMemo(() => {
54+
useEffect(() => {
55+
if (loading) {
56+
return;
57+
}
58+
5059
const subDocs = subDocsData?.pages.flatMap((page) => page.results) || [];
5160

5261
if (treeContext?.root) {
@@ -59,7 +68,7 @@ export const DocSearchSubPageContent = ({
5968
}
6069
}
6170

62-
return {
71+
setDocsData({
6372
groupName: subDocs.length > 0 ? t('Select a doc') : '',
6473
elements: search ? subDocs : [],
6574
emptyString: search ? t('No document found') : t('Search by title'),
@@ -70,8 +79,9 @@ export const DocSearchSubPageContent = ({
7079
},
7180
]
7281
: [],
73-
};
82+
});
7483
}, [
84+
loading,
7585
search,
7686
subDocsData?.pages,
7787
subDocsFetchNextPage,

0 commit comments

Comments
 (0)