Skip to content

Commit 81161a8

Browse files
authored
Merge branch 'main' into taran/resolve-page-meta-links-cr-or-space
2 parents 0f309c2 + f372eec commit 81161a8

File tree

13 files changed

+200
-291
lines changed

13 files changed

+200
-291
lines changed

packages/gitbook/e2e/internal.spec.ts

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,29 @@ import {
3939

4040
const AI_PROMPT = `You're being invoked by the GitBook CI/CD pipeline. To make screenshot testing of the GitBook Assistant visually consistent, look up the title of the first page you find and respond with only EXACTLY its title. To find the page title, invoke the search tool with the query "GitBook". Before invoking the search tool, respond with the exact text: "I'm going to look up 'GitBook' and then respond with only the page title.". Do not execute any other tools or output any other text.`;
4141

42+
const overrideAIInitialState = () => {
43+
const greeting = document.querySelector('[data-testid="ai-chat-time-greeting"]');
44+
if (greeting) {
45+
greeting.textContent = 'Good morning';
46+
}
47+
};
48+
const overrideAIResponse = () => {
49+
const userMessage = document.querySelector('[data-testid="ai-chat-message-user"]');
50+
if (userMessage) {
51+
userMessage.textContent = '[Replaced message] Chat message sent by the user';
52+
}
53+
const assistantMessage = document.querySelectorAll(
54+
'[data-testid="ai-chat-message-assistant"] .ai-response-document'
55+
);
56+
assistantMessage.forEach((message) => {
57+
message.innerHTML = '[Replaced message] AI chat response';
58+
});
59+
const suggestions = document.querySelectorAll('[data-testid="ai-chat-followup-suggestion"]');
60+
suggestions.forEach((suggestion) => {
61+
suggestion.textContent = 'Follow-up suggestion';
62+
});
63+
};
64+
4265
const searchTestCases: Test[] = [
4366
{
4467
name: 'Search - AI Mode: None - Complete flow',
@@ -147,14 +170,7 @@ const searchTestCases: Test[] = [
147170
timeout: 60_000,
148171
});
149172
// Override text content for visual consistency in screenshots
150-
await page.evaluate(() => {
151-
const suggestions = document.querySelectorAll(
152-
'[data-testid="ai-chat-followup-suggestion"]'
153-
);
154-
suggestions.forEach((suggestion) => {
155-
suggestion.textContent = 'Follow-up suggestion';
156-
});
157-
});
173+
await page.evaluate(overrideAIResponse);
158174
},
159175
},
160176
{
@@ -169,12 +185,7 @@ const searchTestCases: Test[] = [
169185
await expect(page.getByTestId('ai-chat')).toBeVisible();
170186
await expect(page.getByTestId('ai-chat-input')).toBeFocused();
171187
// Override text content for visual consistency in screenshots
172-
await page.evaluate(() => {
173-
const greeting = document.querySelector('[data-testid="ai-chat-time-greeting"]');
174-
if (greeting) {
175-
greeting.textContent = 'Good morning';
176-
}
177-
});
188+
await page.evaluate(overrideAIInitialState);
178189
},
179190
},
180191
{
@@ -190,12 +201,7 @@ const searchTestCases: Test[] = [
190201
await expect(page.getByTestId('ai-chat')).toBeVisible();
191202
await expect(page.getByTestId('ai-chat-input')).toBeFocused();
192203
// Override text content for visual consistency in screenshots
193-
await page.evaluate(() => {
194-
const greeting = document.querySelector('[data-testid="ai-chat-time-greeting"]');
195-
if (greeting) {
196-
greeting.textContent = 'Good morning';
197-
}
198-
});
204+
await page.evaluate(overrideAIInitialState);
199205
},
200206
},
201207
{
@@ -211,12 +217,7 @@ const searchTestCases: Test[] = [
211217
await expect(page.getByTestId('ai-chat')).toBeVisible();
212218
await expect(page.getByTestId('ai-chat-input')).toBeFocused();
213219
// Override text content for visual consistency in screenshots
214-
await page.evaluate(() => {
215-
const greeting = document.querySelector('[data-testid="ai-chat-time-greeting"]');
216-
if (greeting) {
217-
greeting.textContent = 'Good morning';
218-
}
219-
});
220+
await page.evaluate(overrideAIInitialState);
220221
},
221222
},
222223
{
@@ -236,14 +237,7 @@ const searchTestCases: Test[] = [
236237
timeout: 60_000,
237238
});
238239
// Override text content for visual consistency in screenshots
239-
await page.evaluate(() => {
240-
const suggestions = document.querySelectorAll(
241-
'[data-testid="ai-chat-followup-suggestion"]'
242-
);
243-
suggestions.forEach((suggestion) => {
244-
suggestion.textContent = 'Follow-up suggestion';
245-
});
246-
});
240+
await page.evaluate(overrideAIResponse);
247241
},
248242
},
249243
];

packages/gitbook/src/components/AI/server-actions/AIMessageView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function AIMessageView(
3535
wrapBlocksInSuspense: false,
3636
withLinkPreviews,
3737
}}
38-
style="mt-2 space-y-4 *:origin-top-left *:animate-blur-in-slow"
38+
style="ai-response-document mt-2 space-y-4 *:origin-top-left *:animate-blur-in-slow"
3939
/>
4040

4141
{withToolCalls && step.toolCalls && step.toolCalls.length > 0 ? (

packages/gitbook/src/components/DocumentView/Annotation/Annotation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function Annotation(props: InlineProps<DocumentInlineAnnotation>) {
2727
ancestorBlocks={[]}
2828
context={context}
2929
nodes={fragment.nodes}
30-
style={['space-y-4']}
30+
style={['contents']}
3131
/>
3232
}
3333
>
Lines changed: 19 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,30 @@
11
'use client';
22

3-
import * as Popover from '@radix-ui/react-popover';
43
import type React from 'react';
54

6-
import { tString, useLanguage } from '@/intl/client';
7-
import { tcls } from '@/lib/tailwind';
5+
import { Tooltip } from '@/components/primitives';
86

97
export function AnnotationPopover(props: { children: React.ReactNode; body: React.ReactNode }) {
108
const { children, body } = props;
11-
const language = useLanguage();
129

1310
return (
14-
<Popover.Root>
15-
<Popover.Trigger asChild>
16-
<button
17-
data-testid="annotation-button"
18-
aria-label={tString(language, 'annotation_button_label')}
19-
className={tcls(
20-
'decoration-dotted',
21-
'decoration-1',
22-
'underline',
23-
'underline-offset-2'
24-
)}
25-
>
26-
{children}
27-
</button>
28-
</Popover.Trigger>
29-
<Popover.Portal>
30-
<Popover.Content
31-
className={tcls(
32-
'text-sm',
33-
'max-w-[280px]',
34-
'bg-tint',
35-
'ring-1',
36-
'ring-tint',
37-
'rounded-sm',
38-
'shadow-1xs',
39-
'shadow-tint-12/1',
40-
'dark:shadow-tint-1/2',
41-
'p-3',
42-
'[&_p]:leading-snug',
43-
'-outline-offset-2',
44-
'outline-2',
45-
'outline-primary/8',
46-
'z-20'
47-
)}
48-
sideOffset={4}
49-
>
50-
{body}
51-
<Popover.Arrow asChild>
52-
<svg
53-
viewBox="0 0 8 5"
54-
className={tcls(
55-
'relative',
56-
'z-2',
57-
'fill-tint-3', // Same as bg-tint
58-
'stroke-tint-7', // Same as ring-tint
59-
'[paint-order:stroke_fill]'
60-
)}
61-
fill="none"
62-
xmlns="http://www.w3.org/2000/svg"
63-
>
64-
<g clip-path="url(#clipAnnotation)">
65-
<path
66-
d="M0 0L4 4L8 0"
67-
strokeWidth="2"
68-
strokeLinecap="round"
69-
stroke="inherit"
70-
fill="inherit"
71-
/>
72-
</g>
73-
<defs>
74-
<clipPath id="clipAnnotation">
75-
<rect width="8" height="5" fill="white" />
76-
</clipPath>
77-
</defs>
78-
</svg>
79-
</Popover.Arrow>
80-
</Popover.Content>
81-
</Popover.Portal>
82-
</Popover.Root>
11+
<Tooltip
12+
label={body}
13+
contentProps={{
14+
role: 'definition',
15+
}}
16+
className="bg-tint-base px-4 py-3 text-sm text-tint-strong shadow-lg shadow-tint-12/4 ring-1 ring-tint-subtle dark:shadow-tint-1"
17+
arrow={true}
18+
arrowProps={{ className: 'fill-tint-1' }}
19+
>
20+
<dfn
21+
data-testid="annotation-button"
22+
className="cursor-help underline decoration-1 decoration-dotted underline-offset-2"
23+
// biome-ignore lint/a11y/noNoninteractiveTabindex: we want to be able to focus the definition to open the tooltip
24+
tabIndex={0}
25+
>
26+
{children}
27+
</dfn>
28+
</Tooltip>
8329
);
8430
}

packages/gitbook/src/components/DocumentView/Update.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ export function Update(props: BlockProps<DocumentBlockUpdate>) {
3535
short: formatDateShort(parsedDate),
3636
}[dateFormat];
3737

38-
const isSticky = parentUpdates.data.sticky;
39-
4038
return (
4139
<div
4240
className={tcls(
@@ -46,9 +44,8 @@ export function Update(props: BlockProps<DocumentBlockUpdate>) {
4644
>
4745
<div
4846
className={tcls(
49-
'h-fit w-40 min-w-40 shrink-0',
50-
// Date is sticky on larger screens & if enabled on the parent Updates block
51-
isSticky && 'md:sticky md:top-[calc(var(--toc-top-offset)+8px)]!'
47+
// Date is only sticky on larger screens when we use flex-row layout
48+
'h-fit w-40 min-w-40 shrink-0 md:sticky md:top-[calc(var(--toc-top-offset)+8px)]!'
5249
)}
5350
>
5451
<time

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">

0 commit comments

Comments
 (0)