Skip to content

Commit 1d8d6d3

Browse files
committed
♻️(frontend) search on all docs if no children
When searching for documents, if no children are found, the search will now include all documents instead of just those with children.
1 parent d12b608 commit 1d8d6d3

File tree

10 files changed

+116
-47
lines changed

10 files changed

+116
-47
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to
88

99
## [Unreleased]
1010

11+
### Changed
12+
13+
- ♻️(frontend) search on all docs if no children #1184
14+
1115
## [3.4.1] - 2025-07-15
1216

1317
### Fixed

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

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect, test } from '@playwright/test';
22

33
import { createDoc, randomName, verifyDocName } from './common';
4+
import { createRootSubPage } from './sub-pages-utils';
45

56
test.beforeEach(async ({ page }) => {
67
await page.goto('/');
@@ -127,12 +128,12 @@ test.describe('Sub page search', () => {
127128
await verifyDocName(page, doc1Title);
128129
const searchButton = page
129130
.getByTestId('left-panel-desktop')
130-
.getByRole('button', { name: 'search' });
131+
.getByRole('button', { name: 'search', exact: true });
131132
await searchButton.click();
132133
const filters = page.getByTestId('doc-search-filters');
133134
await expect(filters).toBeVisible();
134135
await filters.click();
135-
await filters.getByRole('button', { name: 'Current doc' }).click();
136+
await filters.getByRole('button', { name: 'All docs' }).click();
136137
await expect(
137138
page.getByRole('menuitem', { name: 'All docs' }),
138139
).toBeVisible();
@@ -142,40 +143,79 @@ test.describe('Sub page search', () => {
142143
await page.getByRole('menuitem', { name: 'Current doc' }).click();
143144

144145
await expect(page.getByRole('button', { name: 'Reset' })).toBeVisible();
146+
147+
await page.getByRole('button', { name: 'close' }).click();
148+
149+
await createRootSubPage(page, browserName, 'My sub page search');
150+
151+
await searchButton.click();
152+
await expect(
153+
filters.getByRole('button', { name: 'Current doc' }),
154+
).toBeVisible();
145155
});
146156

147157
test('it searches sub pages', async ({ page, browserName }) => {
148158
await page.goto('/');
149159

150-
const [doc1Title] = await createDoc(
160+
// First doc
161+
const [firstDocTitle] = await createDoc(
151162
page,
152-
'My sub page search',
163+
'My first sub page search',
153164
browserName,
154165
1,
155166
);
156-
await verifyDocName(page, doc1Title);
167+
await verifyDocName(page, firstDocTitle);
168+
169+
// Create a new doc - for the moment without children
157170
await page.getByRole('button', { name: 'New doc' }).click();
158171
await verifyDocName(page, '');
159172
await page.getByRole('textbox', { name: 'doc title input' }).click();
160173
await page
161174
.getByRole('textbox', { name: 'doc title input' })
162175
.press('ControlOrMeta+a');
163-
const [randomDocName] = randomName('doc-sub-page', browserName, 1);
176+
const [secondDocTitle] = randomName(
177+
'My second sub page search',
178+
browserName,
179+
1,
180+
);
164181
await page
165182
.getByRole('textbox', { name: 'doc title input' })
166-
.fill(randomDocName);
183+
.fill(secondDocTitle);
184+
167185
const searchButton = page
168186
.getByTestId('left-panel-desktop')
169187
.getByRole('button', { name: 'search' });
170188

171189
await searchButton.click();
190+
await expect(page.getByRole('button', { name: 'All docs' })).toBeVisible();
191+
await page.getByRole('combobox', { name: 'Quick search input' }).click();
192+
await page
193+
.getByRole('combobox', { name: 'Quick search input' })
194+
.fill('sub page search');
195+
196+
// Expect to find the first doc
172197
await expect(
173-
page.getByRole('button', { name: 'Current doc' }),
198+
page.getByRole('presentation').getByLabel(firstDocTitle),
174199
).toBeVisible();
175-
await page.getByRole('combobox', { name: 'Quick search input' }).click();
200+
await expect(
201+
page.getByRole('presentation').getByLabel(secondDocTitle),
202+
).toBeVisible();
203+
204+
await page.getByRole('button', { name: 'close' }).click();
205+
206+
// Create a sub page
207+
await createRootSubPage(page, browserName, secondDocTitle);
208+
await searchButton.click();
176209
await page
177210
.getByRole('combobox', { name: 'Quick search input' })
178-
.fill('sub');
179-
await expect(page.getByLabel(randomDocName)).toBeVisible();
211+
.fill('sub page search');
212+
213+
// Now there is a sub page - expect to have the focus on the current doc
214+
await expect(
215+
page.getByRole('presentation').getByLabel(secondDocTitle),
216+
).toBeVisible();
217+
await expect(
218+
page.getByRole('presentation').getByLabel(firstDocTitle),
219+
).toBeHidden();
180220
});
181221
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './useCollaboration';
22
export * from './useCopyDocLink';
3+
export * from './useDocUtils';
34
export * from './useIsCollaborativeEditable';
45
export * from './useTrans';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Doc } from '@/docs/doc-management';
2+
3+
export const useDocUtils = (doc: Doc) => {
4+
return {
5+
isParent: doc.depth === 1, // it is a parent
6+
isChild: doc.depth > 1, // it is a child
7+
hasChildren: doc.numchild > 0, // it has children
8+
isDesyncronized: !!(
9+
doc.ancestors_link_reach &&
10+
doc.ancestors_link_role &&
11+
(doc.computed_link_reach !== doc.ancestors_link_reach ||
12+
doc.computed_link_role !== doc.ancestors_link_role)
13+
),
14+
} as const;
15+
};

src/frontend/apps/impress/src/features/docs/doc-share/components/DocVisibility.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import {
2222
LinkReach,
2323
LinkRole,
2424
getDocLinkReach,
25+
useDocUtils,
2526
useUpdateDocLink,
2627
} from '@/docs/doc-management';
27-
import { useTreeUtils } from '@/docs/doc-tree';
2828
import { useResponsiveStore } from '@/stores';
2929

3030
import { useTranslatedShareSettings } from '../hooks/';
@@ -47,7 +47,7 @@ export const DocVisibility = ({ doc, canEdit = true }: DocVisibilityProps) => {
4747
const [docLinkRole, setDocLinkRole] = useState<LinkRole>(
4848
doc.computed_link_role ?? LinkRole.READER,
4949
);
50-
const { isDesyncronized } = useTreeUtils(doc);
50+
const { isDesyncronized } = useDocUtils(doc);
5151

5252
const { linkModeTranslations, linkReachChoices, linkReachTranslations } =
5353
useTranslatedShareSettings();

src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItemActions.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ import { useTranslation } from 'react-i18next';
1010
import { css } from 'styled-components';
1111

1212
import { Box, BoxButton, Icon } from '@/components';
13-
1413
import {
1514
Doc,
1615
ModalRemoveDoc,
1716
Role,
1817
useCopyDocLink,
19-
} from '../../doc-management';
18+
useDocUtils,
19+
} from '@/docs/doc-management';
20+
2021
import { useCreateChildrenDoc } from '../api/useCreateChildren';
2122
import { useDetachDoc } from '../api/useDetach';
2223
import MoveDocIcon from '../assets/doc-extract-bold.svg';
23-
import { useTreeUtils } from '../hooks';
2424

2525
type DocTreeItemActionsProps = {
2626
doc: Doc;
@@ -42,7 +42,7 @@ export const DocTreeItemActions = ({
4242
const deleteModal = useModal();
4343

4444
const copyLink = useCopyDocLink(doc.id);
45-
const { isCurrentParent } = useTreeUtils(doc);
45+
const { isParent } = useDocUtils(doc);
4646
const { mutate: detachDoc } = useDetachDoc();
4747
const treeContext = useTreeContext<Doc>();
4848

@@ -71,7 +71,7 @@ export const DocTreeItemActions = ({
7171
icon: <Icon iconName="link" $size="24px" />,
7272
callback: copyLink,
7373
},
74-
...(!isCurrentParent
74+
...(!isParent
7575
? [
7676
{
7777
label: t('Move to my docs'),

src/frontend/apps/impress/src/features/docs/doc-tree/hooks/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/frontend/apps/impress/src/features/docs/doc-tree/hooks/useTreeUtils.tsx

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
export * from './api';
2-
export * from './hooks';
32
export * from './utils';

src/frontend/apps/impress/src/features/left-panel/components/LeftPanelHeader.tsx

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
33
import { PropsWithChildren, useCallback, useState } from 'react';
44

55
import { Box, Icon, SeparatedSection } from '@/components';
6+
import { Doc, useDocStore, useDocUtils } from '@/docs/doc-management';
67
import { DocSearchModal, DocSearchTarget } from '@/docs/doc-search/';
78
import { useAuth } from '@/features/auth';
89
import { useCmdK } from '@/hook/useCmdK';
@@ -12,10 +13,10 @@ import { useLeftPanelStore } from '../stores';
1213
import { LeftPanelHeaderButton } from './LeftPanelHeaderButton';
1314

1415
export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
16+
const { currentDoc } = useDocStore();
17+
const isDoc = !!currentDoc;
1518
const router = useRouter();
1619
const { authenticated } = useAuth();
17-
const isDoc = router.pathname === '/docs/[id]';
18-
1920
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
2021

2122
const openSearchModal = useCallback(() => {
@@ -77,16 +78,45 @@ export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
7778
</SeparatedSection>
7879
{children}
7980
</Box>
80-
{isSearchModalOpen && (
81-
<DocSearchModal
81+
{isSearchModalOpen && isDoc && (
82+
<LeftPanelSearchModalFilter
8283
onClose={closeSearchModal}
8384
isOpen={isSearchModalOpen}
84-
showFilters={isDoc}
85-
defaultFilters={{
86-
target: isDoc ? DocSearchTarget.CURRENT : undefined,
87-
}}
85+
doc={currentDoc}
8886
/>
8987
)}
88+
{isSearchModalOpen && !isDoc && (
89+
<DocSearchModal onClose={closeSearchModal} isOpen={isSearchModalOpen} />
90+
)}
9091
</>
9192
);
9293
};
94+
95+
interface LeftPanelSearchModalProps {
96+
doc: Doc;
97+
isOpen: boolean;
98+
onClose: () => void;
99+
}
100+
101+
const LeftPanelSearchModalFilter = ({
102+
doc,
103+
isOpen,
104+
onClose,
105+
}: LeftPanelSearchModalProps) => {
106+
const { hasChildren, isChild } = useDocUtils(doc);
107+
const isWithChildren = isChild || hasChildren;
108+
109+
let defaultFilters = DocSearchTarget.ALL;
110+
if (isWithChildren) {
111+
defaultFilters = DocSearchTarget.CURRENT;
112+
}
113+
114+
return (
115+
<DocSearchModal
116+
onClose={onClose}
117+
isOpen={isOpen}
118+
showFilters={true}
119+
defaultFilters={{ target: defaultFilters }}
120+
/>
121+
);
122+
};

0 commit comments

Comments
 (0)