Skip to content

Commit 34b0c67

Browse files
authored
Stop filtering by language when site space language is unspecified (#3825)
1 parent e0ace99 commit 34b0c67

File tree

5 files changed

+132
-170
lines changed

5 files changed

+132
-170
lines changed

packages/gitbook/src/components/Header/Header.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,8 @@ export function Header(props: {
151151
encodeClientSiteSections(context, sections).current
152152
: undefined
153153
}
154-
spaceTitle={siteSpace.title}
155-
siteSpaceId={siteSpace.id}
156-
siteSpaceIds={siteSpaces
157-
.filter((s) => s.space.language === siteSpace.space.language)
158-
.map((s) => s.id)}
154+
siteSpace={siteSpace}
155+
siteSpaces={siteSpaces}
159156
viewport={!withTopHeader ? 'mobile' : undefined}
160157
/>
161158
</div>

packages/gitbook/src/components/Search/SearchContainer.tsx

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { t, useLanguage } from '@/intl/client';
4-
import { CustomizationSearchStyle, type SiteSection } from '@gitbook/api';
4+
import { CustomizationSearchStyle, type SiteSection, type SiteSpace } from '@gitbook/api';
55
import { useRouter } from 'next/navigation';
66
import React, { useRef } from 'react';
77
import { useHotkeys } from 'react-hotkeys-hook';
@@ -21,14 +21,11 @@ import { useSearchResults } from './useSearchResults';
2121
import { useSearchResultsCursor } from './useSearchResultsCursor';
2222

2323
interface SearchContainerProps {
24-
/** The current site space id. */
25-
siteSpaceId: string;
24+
/** The current site space. */
25+
siteSpace: SiteSpace;
2626

27-
/** The title of the current space. */
28-
spaceTitle: string;
29-
30-
/** The ids of all spaces in the current section. */
31-
siteSpaceIds: string[];
27+
/** All site spaces in the current section. */
28+
siteSpaces: ReadonlyArray<SiteSpace>;
3229

3330
/** Whether there are sections on the site. */
3431
withSections: boolean;
@@ -50,20 +47,17 @@ interface SearchContainerProps {
5047
/**
5148
* Client component to render the search input and results.
5249
*/
53-
export function SearchContainer(props: SearchContainerProps) {
54-
const {
55-
siteSpaceId,
56-
spaceTitle,
57-
section,
58-
withVariants,
59-
withSiteVariants,
60-
withSections,
61-
style,
62-
className,
63-
viewport,
64-
siteSpaceIds,
65-
} = props;
66-
50+
export function SearchContainer({
51+
siteSpace,
52+
section,
53+
withVariants,
54+
withSiteVariants,
55+
withSections,
56+
style,
57+
className,
58+
viewport,
59+
siteSpaces,
60+
}: SearchContainerProps) {
6761
const { assistants } = useAI();
6862

6963
const [state, setSearchState] = useSearch();
@@ -181,10 +175,29 @@ export function SearchContainer(props: SearchContainerProps) {
181175
const visible = viewport === 'desktop' ? !isMobile : viewport === 'mobile' ? isMobile : true;
182176

183177
const searchResultsId = `search-results-${React.useId()}`;
178+
179+
// If searching all variants of the current section (the "extended" scope),
180+
// filter by language if the language is set for both the current and the target site space.
181+
const siteSpaceIds = React.useMemo(
182+
() =>
183+
siteSpaces.reduce((acc: string[], ss) => {
184+
if (
185+
!siteSpace.space.language ||
186+
!ss.space.language ||
187+
ss.space.language === siteSpace.space.language
188+
) {
189+
acc.push(ss.id);
190+
}
191+
192+
return acc;
193+
}, []),
194+
[siteSpaces, siteSpace.space.language]
195+
);
196+
184197
const { results, fetching, error } = useSearchResults({
185198
disabled: !(state?.query || withAI),
186199
query: normalizedQuery,
187-
siteSpaceId,
200+
siteSpaceId: siteSpace.id,
188201
siteSpaceIds,
189202
scope: state?.scope ?? 'default',
190203
withAI: withAI,
@@ -233,7 +246,7 @@ export function SearchContainer(props: SearchContainerProps) {
233246
<div className="border-tint-subtle border-t bg-tint-subtle px-4 py-1.5">
234247
<SearchScopeControl
235248
section={section}
236-
spaceTitle={spaceTitle}
249+
spaceTitle={siteSpace.title}
237250
withVariants={withVariants}
238251
withSiteVariants={withSiteVariants}
239252
withSections={withSections}
@@ -314,8 +327,7 @@ export function SearchContainer(props: SearchContainerProps) {
314327
* Screen reader announcement for search results.
315328
* Without it there is no feedback for screen reader users when a search returns no results.
316329
*/
317-
function LiveResultsAnnouncer(props: { count: number; showing: boolean }) {
318-
const { count, showing } = props;
330+
function LiveResultsAnnouncer({ count, showing }: { count: number; showing: boolean }) {
319331
const language = useLanguage();
320332
return (
321333
<div className="sr-only" aria-live="assertive" role="alert" aria-relevant="all">

packages/gitbook/src/components/Search/server-actions.tsx

Lines changed: 66 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -66,53 +66,6 @@ export interface AskAnswerResult {
6666
sources: AskAnswerSource[];
6767
}
6868

69-
/**
70-
* Server action to search content in the entire site.
71-
*/
72-
export async function searchAllSiteContent(query: string): Promise<OrderedComputedResult[]> {
73-
return traceErrorOnly('Search.searchAllSiteContent', async () => {
74-
const context = await getServerActionBaseContext();
75-
return searchSiteContent(context, {
76-
query,
77-
scope: { mode: 'all' },
78-
});
79-
});
80-
}
81-
82-
/**
83-
* Server action to search content in a space.
84-
*/
85-
export async function searchCurrentSiteSpaceContent(
86-
query: string,
87-
siteSpaceId: string
88-
): Promise<OrderedComputedResult[]> {
89-
return traceErrorOnly('Search.searchSiteSpaceContent', async () => {
90-
const context = await getServerActionBaseContext();
91-
92-
return await searchSiteContent(context, {
93-
query,
94-
scope: { mode: 'current', siteSpaceId },
95-
});
96-
});
97-
}
98-
99-
/**
100-
* Server action to search content in a specific space.
101-
*/
102-
export async function searchSpecificSiteSpaceContent(
103-
query: string,
104-
siteSpaceIds: string[]
105-
): Promise<OrderedComputedResult[]> {
106-
return traceErrorOnly('Search.searchSiteSpaceContent', async () => {
107-
const context = await getServerActionBaseContext();
108-
109-
return await searchSiteContent(context, {
110-
query,
111-
scope: { mode: 'specific', siteSpaceIds },
112-
});
113-
});
114-
}
115-
11669
/**
11770
* Server action to ask a question in a space.
11871
*/
@@ -245,69 +198,72 @@ export async function streamRecommendedQuestions(args: { siteSpaceId?: string })
245198
/**
246199
* Search for content in a site by scoping the search to all content, a specific spaces or current space.
247200
*/
248-
async function searchSiteContent(
249-
context: GitBookBaseContext,
250-
args: {
251-
query: string;
252-
scope:
253-
| { mode: 'all' }
254-
| { mode: 'current'; siteSpaceId: string }
255-
| { mode: 'specific'; siteSpaceIds: string[] };
256-
}
257-
): Promise<OrderedComputedResult[]> {
258-
const { dataFetcher } = context;
259-
const siteURLData = await getSiteURLDataFromMiddleware();
260-
261-
const { scope, query } = args;
262-
263-
if (query.length <= 1) {
264-
return [];
265-
}
266-
267-
const [searchResults, { structure }] = await Promise.all([
268-
throwIfDataError(
269-
dataFetcher.searchSiteContent({
270-
organizationId: siteURLData.organization,
271-
siteId: siteURLData.site,
272-
query,
273-
scope,
274-
})
275-
),
276-
throwIfDataError(
277-
dataFetcher.getPublishedContentSite({
278-
organizationId: siteURLData.organization,
279-
siteId: siteURLData.site,
280-
siteShareKey: siteURLData.shareKey,
281-
})
282-
),
283-
]);
284-
285-
return (
286-
await Promise.all(
287-
searchResults.map((spaceItem) => {
288-
const found = findSiteSpaceBy(
289-
structure,
290-
(siteSpace) => siteSpace.space.id === spaceItem.id
291-
);
292-
const siteSection = found?.siteSection;
293-
const siteSectionGroup = found?.siteSectionGroup;
294-
295-
return Promise.all(
296-
spaceItem.pages.map((pageItem) =>
297-
transformSitePageResult(context, {
298-
pageItem,
299-
spaceItem,
300-
siteSpace: found?.siteSpace,
301-
space: found?.siteSpace.space,
302-
spaceURL: found?.siteSpace.urls.published,
303-
siteSection: siteSection ?? undefined,
304-
siteSectionGroup: (siteSectionGroup as SiteSectionGroup) ?? undefined,
305-
})
306-
)
307-
);
308-
})
309-
)
310-
).flat(2);
201+
export async function searchSiteContent({
202+
query,
203+
...scope
204+
}: {
205+
query: string;
206+
} & (
207+
| { mode: 'all' }
208+
| { mode: 'current'; siteSpaceId: string }
209+
| { mode: 'specific'; siteSpaceIds: string[] }
210+
)): Promise<OrderedComputedResult[]> {
211+
return traceErrorOnly(`Search.searchSiteContent.${scope.mode}`, async () => {
212+
if (query.length <= 1) {
213+
return [];
214+
}
215+
216+
const [context, { organization, site, shareKey }] = await Promise.all([
217+
getServerActionBaseContext(),
218+
getSiteURLDataFromMiddleware(),
219+
]);
220+
221+
const [searchResults, { structure }] = await Promise.all([
222+
throwIfDataError(
223+
context.dataFetcher.searchSiteContent({
224+
organizationId: organization,
225+
siteId: site,
226+
query,
227+
scope,
228+
})
229+
),
230+
throwIfDataError(
231+
context.dataFetcher.getPublishedContentSite({
232+
organizationId: organization,
233+
siteId: site,
234+
siteShareKey: shareKey,
235+
})
236+
),
237+
]);
238+
239+
return (
240+
await Promise.all(
241+
searchResults.map((spaceItem) => {
242+
const found = findSiteSpaceBy(
243+
structure,
244+
(siteSpace) => siteSpace.space.id === spaceItem.id
245+
);
246+
const siteSection = found?.siteSection;
247+
const siteSectionGroup = found?.siteSectionGroup;
248+
249+
return Promise.all(
250+
spaceItem.pages.map((pageItem) =>
251+
transformSitePageResult(context, {
252+
pageItem,
253+
spaceItem,
254+
siteSpace: found?.siteSpace,
255+
space: found?.siteSpace.space,
256+
spaceURL: found?.siteSpace.urls.published,
257+
siteSection: siteSection ?? undefined,
258+
siteSectionGroup:
259+
(siteSectionGroup as SiteSectionGroup) ?? undefined,
260+
})
261+
)
262+
);
263+
})
264+
)
265+
).flat(2);
266+
});
311267
}
312268

313269
async function transformAnswer(

packages/gitbook/src/components/Search/useSearchResults.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import { assert } from 'ts-essentials';
55

66
import {
77
type OrderedComputedResult,
8-
searchAllSiteContent,
9-
searchCurrentSiteSpaceContent,
10-
searchSpecificSiteSpaceContent,
8+
searchSiteContent,
119
streamRecommendedQuestions,
1210
} from './server-actions';
1311

1412
import { type Assistant, useAI } from '@/components/AI';
13+
import assertNever from 'assert-never';
1514
import { useTrackEvent } from '../Insights';
1615
import { isQuestion } from './isQuestion';
1716
import type { SearchScope } from './useSearch';
@@ -123,23 +122,26 @@ export function useSearchResults(props: {
123122
const timeout = setTimeout(async () => {
124123
try {
125124
const results = await (() => {
126-
if (scope === 'all') {
127-
// Search all content on the site
128-
return searchAllSiteContent(query);
125+
switch (scope) {
126+
case 'all':
127+
// Search all content on the site
128+
return searchSiteContent({ query, mode: 'all' });
129+
case 'default':
130+
// Search the current section's variant + matched/default variant for other sections
131+
return searchSiteContent({ query, mode: 'current', siteSpaceId });
132+
case 'extended':
133+
// Search all variants of the current section
134+
return searchSiteContent({ query, mode: 'specific', siteSpaceIds });
135+
case 'current':
136+
// Search only the current section's current variant
137+
return searchSiteContent({
138+
query,
139+
mode: 'specific',
140+
siteSpaceIds: [siteSpaceId],
141+
});
142+
default:
143+
assertNever(scope);
129144
}
130-
if (scope === 'default') {
131-
// Search the current section's variant + matched/default variant for other sections
132-
return searchCurrentSiteSpaceContent(query, siteSpaceId);
133-
}
134-
if (scope === 'extended') {
135-
// Search all variants of the current section
136-
return searchSpecificSiteSpaceContent(query, siteSpaceIds);
137-
}
138-
if (scope === 'current') {
139-
// Search only the current section's current variant
140-
return searchSpecificSiteSpaceContent(query, [siteSpaceId]);
141-
}
142-
throw new Error(`Unhandled search scope: ${scope}`);
143145
})();
144146

145147
if (cancelled) {

0 commit comments

Comments
 (0)