mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 01:53:26 +00:00
Unify layout and fix tracking of ask_question (#3610)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix event ask_question not being tracked
|
||||
+13
-2
@@ -5,6 +5,8 @@ import {
|
||||
generateEmbeddableViewport,
|
||||
} from '@/components/Embeddable';
|
||||
import { getEmbeddableStaticContext } from '@/lib/embeddable';
|
||||
import { shouldTrackEvents } from '@/lib/tracking';
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
interface SiteStaticLayoutProps {
|
||||
params: Promise<RouteLayoutParams>;
|
||||
@@ -14,9 +16,18 @@ export default async function RootLayout({
|
||||
params,
|
||||
children,
|
||||
}: React.PropsWithChildren<SiteStaticLayoutProps>) {
|
||||
const { context } = await getEmbeddableStaticContext(await params);
|
||||
const { context, visitorAuthClaims } = await getEmbeddableStaticContext(await params);
|
||||
const withTracking = shouldTrackEvents(await headers());
|
||||
|
||||
return <EmbeddableRootLayout context={context}>{children}</EmbeddableRootLayout>;
|
||||
return (
|
||||
<EmbeddableRootLayout
|
||||
context={context}
|
||||
withTracking={withTracking}
|
||||
visitorAuthClaims={visitorAuthClaims}
|
||||
>
|
||||
{children}
|
||||
</EmbeddableRootLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateViewport({ params }: SiteStaticLayoutProps) {
|
||||
|
||||
+12
-2
@@ -5,6 +5,7 @@ import {
|
||||
generateEmbeddableViewport,
|
||||
} from '@/components/Embeddable';
|
||||
import { getEmbeddableStaticContext } from '@/lib/embeddable';
|
||||
import { shouldTrackEvents } from '@/lib/tracking';
|
||||
|
||||
interface SiteStaticLayoutProps {
|
||||
params: Promise<RouteLayoutParams>;
|
||||
@@ -14,9 +15,18 @@ export default async function RootLayout({
|
||||
params,
|
||||
children,
|
||||
}: React.PropsWithChildren<SiteStaticLayoutProps>) {
|
||||
const { context } = await getEmbeddableStaticContext(await params);
|
||||
const { context, visitorAuthClaims } = await getEmbeddableStaticContext(await params);
|
||||
const withTracking = shouldTrackEvents();
|
||||
|
||||
return <EmbeddableRootLayout context={context}>{children}</EmbeddableRootLayout>;
|
||||
return (
|
||||
<EmbeddableRootLayout
|
||||
context={context}
|
||||
withTracking={withTracking}
|
||||
visitorAuthClaims={visitorAuthClaims}
|
||||
>
|
||||
{children}
|
||||
</EmbeddableRootLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateViewport({ params }: SiteStaticLayoutProps) {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './useAI';
|
||||
export * from './useAIChat';
|
||||
export type { RenderAIMessageOptions } from './server-actions';
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { AIChatProvider, AIContextProvider } from '@/components/AI';
|
||||
import { AIContextProvider } from '@/components/AI';
|
||||
import { CustomizationRootLayout } from '@/components/RootLayout';
|
||||
import {
|
||||
SiteLayoutClientContexts,
|
||||
generateSiteLayoutMetadata,
|
||||
generateSiteLayoutViewport,
|
||||
} from '@/components/SiteLayout';
|
||||
import type { VisitorAuthClaims } from '@/lib/adaptive';
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
import { CustomizationAIMode } from '@gitbook/api';
|
||||
import { SpaceLayoutServerContext } from '../SpaceLayout';
|
||||
import { EmbeddableIframeAPI } from './EmbeddableIframeAPI';
|
||||
|
||||
type EmbeddableRootLayoutProps = {
|
||||
context: GitBookSiteContext;
|
||||
withTracking: boolean;
|
||||
visitorAuthClaims: VisitorAuthClaims;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -18,6 +22,8 @@ type EmbeddableRootLayoutProps = {
|
||||
*/
|
||||
export async function EmbeddableRootLayout({
|
||||
context,
|
||||
withTracking,
|
||||
visitorAuthClaims,
|
||||
children,
|
||||
}: React.PropsWithChildren<EmbeddableRootLayoutProps>) {
|
||||
return (
|
||||
@@ -31,8 +37,11 @@ export async function EmbeddableRootLayout({
|
||||
aiMode={CustomizationAIMode.Assistant}
|
||||
trademark={context.customization.trademark.enabled}
|
||||
>
|
||||
<AIChatProvider
|
||||
renderMessageOptions={{
|
||||
<SpaceLayoutServerContext
|
||||
context={context}
|
||||
withTracking={withTracking}
|
||||
visitorAuthClaims={visitorAuthClaims}
|
||||
aiChatRenderMessageOptions={{
|
||||
withLinkPreviews: false,
|
||||
asEmbeddable: true,
|
||||
}}
|
||||
@@ -41,17 +50,17 @@ export async function EmbeddableRootLayout({
|
||||
<EmbeddableIframeAPI
|
||||
baseURL={context.linker.toPathInSpace('~gitbook/embed/')}
|
||||
/>
|
||||
</AIChatProvider>
|
||||
</SpaceLayoutServerContext>
|
||||
</AIContextProvider>
|
||||
</SiteLayoutClientContexts>
|
||||
</CustomizationRootLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateEmbeddableViewport({ context }: EmbeddableRootLayoutProps) {
|
||||
export async function generateEmbeddableViewport({ context }: { context: GitBookSiteContext }) {
|
||||
return generateSiteLayoutViewport(context);
|
||||
}
|
||||
|
||||
export async function generateEmbeddableMetadata({ context }: EmbeddableRootLayoutProps) {
|
||||
export async function generateEmbeddableMetadata({ context }: { context: GitBookSiteContext }) {
|
||||
return generateSiteLayoutMetadata(context);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ type TrackEventCallback = <EventName extends InsightsEventName>(
|
||||
options?: InsightsEventOptions
|
||||
) => void;
|
||||
|
||||
const InsightsContext = React.createContext<TrackEventCallback>(() => {});
|
||||
const InsightsContext = React.createContext<TrackEventCallback>(() => {
|
||||
console.error('useTrackEvent must be used within an InsightsProvider');
|
||||
});
|
||||
|
||||
interface InsightsProviderProps {
|
||||
/** If true, the events will be sent to the server. */
|
||||
|
||||
@@ -13,7 +13,7 @@ import { buildVersion } from '@/lib/build';
|
||||
import { GITBOOK_API_PUBLIC_URL, GITBOOK_ASSETS_URL, GITBOOK_ICONS_URL } from '@/lib/env';
|
||||
import { getResizedImageURL } from '@/lib/images';
|
||||
import { isSiteIndexable } from '@/lib/seo';
|
||||
import { AIChatProvider, AIContextProvider } from '../AI';
|
||||
import { AIContextProvider } from '../AI';
|
||||
import { RocketLoaderDetector } from './RocketLoaderDetector';
|
||||
import { SiteLayoutClientContexts } from './SiteLayoutClientContexts';
|
||||
|
||||
@@ -58,15 +58,13 @@ export async function SiteLayout(props: {
|
||||
aiMode={customization.ai?.mode}
|
||||
trademark={customization.trademark.enabled}
|
||||
>
|
||||
<AIChatProvider>
|
||||
<SpaceLayout
|
||||
context={context}
|
||||
withTracking={withTracking}
|
||||
visitorAuthClaims={visitorAuthClaims}
|
||||
>
|
||||
{children}
|
||||
</SpaceLayout>
|
||||
</AIChatProvider>
|
||||
<SpaceLayout
|
||||
context={context}
|
||||
withTracking={withTracking}
|
||||
visitorAuthClaims={visitorAuthClaims}
|
||||
>
|
||||
{children}
|
||||
</SpaceLayout>
|
||||
</AIContextProvider>
|
||||
|
||||
{scripts.length > 0 ? (
|
||||
|
||||
@@ -14,6 +14,8 @@ import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import type { VisitorAuthClaims } from '@/lib/adaptive';
|
||||
import { GITBOOK_APP_URL } from '@/lib/env';
|
||||
import { AIChatProvider } from '../AI';
|
||||
import type { RenderAIMessageOptions } from '../AI';
|
||||
import { AIChat } from '../AIChat';
|
||||
import { Announcement } from '../Announcement';
|
||||
import { SpacesDropdown } from '../Header/SpacesDropdown';
|
||||
@@ -23,10 +25,7 @@ import { SiteSectionList, encodeClientSiteSections } from '../SiteSections';
|
||||
import { CurrentContentProvider } from '../hooks';
|
||||
import { SpaceLayoutContextProvider } from './SpaceLayoutContext';
|
||||
|
||||
/**
|
||||
* Render the entire layout of the space (header, table of contents, footer).
|
||||
*/
|
||||
export function SpaceLayout(props: {
|
||||
type SpaceLayoutProps = {
|
||||
context: GitBookSiteContext;
|
||||
|
||||
/** Whether to enable tracking of events into site insights. */
|
||||
@@ -35,22 +34,21 @@ export function SpaceLayout(props: {
|
||||
/** The visitor auth claims. */
|
||||
visitorAuthClaims: VisitorAuthClaims;
|
||||
|
||||
/** The options for rendering AI messages. */
|
||||
aiChatRenderMessageOptions?: RenderAIMessageOptions;
|
||||
|
||||
/** The children of the layout. */
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { context, withTracking, visitorAuthClaims, children } = props;
|
||||
const { siteSpace, customization, sections, siteSpaces } = context;
|
||||
};
|
||||
|
||||
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
|
||||
/**
|
||||
* Provide all contexts for a space.
|
||||
*/
|
||||
export function SpaceLayoutServerContext(props: SpaceLayoutProps) {
|
||||
const { context, withTracking, visitorAuthClaims, aiChatRenderMessageOptions, children } =
|
||||
props;
|
||||
|
||||
const withSections = Boolean(sections && sections.list.length > 1);
|
||||
const isMultiVariants = Boolean(siteSpaces.length > 1);
|
||||
|
||||
const withFooter =
|
||||
customization.themes.toggeable ||
|
||||
customization.footer.copyright ||
|
||||
customization.footer.logo ||
|
||||
customization.footer.groups?.length;
|
||||
const { customization } = context;
|
||||
|
||||
const eventUrl = new URL(
|
||||
context.linker.toAbsoluteURL(context.linker.toPathInSite('/~gitbook/__evt'))
|
||||
@@ -76,92 +74,116 @@ export function SpaceLayout(props: {
|
||||
eventUrl={eventUrl.toString()}
|
||||
visitorCookieTrackingEnabled={customization.insights?.trackingCookie}
|
||||
>
|
||||
<Announcement context={context} />
|
||||
<Header withTopHeader={withTopHeader} context={context} />
|
||||
{customization.ai?.mode === CustomizationAIMode.Assistant ? (
|
||||
<AIChat trademark={customization.trademark.enabled} />
|
||||
) : null}
|
||||
|
||||
<div className="motion-safe:transition-all motion-safe:duration-300 lg:chat-open:mr-80 xl:chat-open:mr-96">
|
||||
<div
|
||||
className={tcls(
|
||||
'flex',
|
||||
'flex-col',
|
||||
'lg:flex-row',
|
||||
CONTAINER_STYLE,
|
||||
'site-width-wide:max-w-full',
|
||||
|
||||
// Ensure the footer is display below the viewport even if the content is not enough
|
||||
withFooter && [
|
||||
'site-header:min-h-[calc(100vh-64px)]',
|
||||
'site-header-sections:min-h-[calc(100vh-108px)]',
|
||||
],
|
||||
withTopHeader ? null : 'lg:min-h-screen'
|
||||
)}
|
||||
>
|
||||
<TableOfContents
|
||||
context={context}
|
||||
header={
|
||||
withTopHeader ? null : (
|
||||
<div
|
||||
className={tcls(
|
||||
'hidden',
|
||||
'pr-4',
|
||||
'lg:flex',
|
||||
'grow-0',
|
||||
'flex-wrap',
|
||||
'dark:shadow-light/1',
|
||||
'text-base/tight'
|
||||
)}
|
||||
>
|
||||
<HeaderLogo context={context} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
innerHeader={
|
||||
// displays the search button and/or the space dropdown in the ToC according to the header/variant settings. E.g if there is no header, the search button will be displayed in the ToC.
|
||||
<>
|
||||
{!withTopHeader && (
|
||||
<SearchContainer
|
||||
style={CustomizationSearchStyle.Subtle}
|
||||
isMultiVariants={siteSpaces.length > 1}
|
||||
spaceTitle={siteSpace.title}
|
||||
siteSpaceId={siteSpace.id}
|
||||
className="max-lg:hidden"
|
||||
viewport="desktop"
|
||||
/>
|
||||
)}
|
||||
{!withTopHeader && withSections && sections && (
|
||||
<SiteSectionList
|
||||
className={tcls('hidden', 'lg:block')}
|
||||
sections={encodeClientSiteSections(
|
||||
context,
|
||||
sections
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{isMultiVariants && !sections && (
|
||||
<SpacesDropdown
|
||||
context={context}
|
||||
siteSpace={siteSpace}
|
||||
siteSpaces={siteSpaces}
|
||||
className={tcls(
|
||||
'w-full',
|
||||
'page-no-toc:hidden',
|
||||
'page-no-toc:site-header-none:flex'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div className="flex min-w-0 flex-1 flex-col">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{withFooter ? <Footer context={context} /> : null}
|
||||
<AIChatProvider renderMessageOptions={aiChatRenderMessageOptions}>
|
||||
{children}
|
||||
</AIChatProvider>
|
||||
</InsightsProvider>
|
||||
</CurrentContentProvider>
|
||||
</SpaceLayoutContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the entire layout of the space (header, table of contents, footer).
|
||||
*/
|
||||
export function SpaceLayout(props: SpaceLayoutProps) {
|
||||
const { context, children } = props;
|
||||
const { siteSpace, customization, sections, siteSpaces } = context;
|
||||
|
||||
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
|
||||
|
||||
const withSections = Boolean(sections && sections.list.length > 1);
|
||||
const isMultiVariants = Boolean(siteSpaces.length > 1);
|
||||
|
||||
const withFooter =
|
||||
customization.themes.toggeable ||
|
||||
customization.footer.copyright ||
|
||||
customization.footer.logo ||
|
||||
customization.footer.groups?.length;
|
||||
|
||||
return (
|
||||
<SpaceLayoutServerContext {...props}>
|
||||
<Announcement context={context} />
|
||||
<Header withTopHeader={withTopHeader} context={context} />
|
||||
{customization.ai?.mode === CustomizationAIMode.Assistant ? (
|
||||
<AIChat trademark={customization.trademark.enabled} />
|
||||
) : null}
|
||||
|
||||
<div className="motion-safe:transition-all motion-safe:duration-300 lg:chat-open:mr-80 xl:chat-open:mr-96">
|
||||
<div
|
||||
className={tcls(
|
||||
'flex',
|
||||
'flex-col',
|
||||
'lg:flex-row',
|
||||
CONTAINER_STYLE,
|
||||
'site-width-wide:max-w-full',
|
||||
|
||||
// Ensure the footer is display below the viewport even if the content is not enough
|
||||
withFooter && [
|
||||
'site-header:min-h-[calc(100vh-64px)]',
|
||||
'site-header-sections:min-h-[calc(100vh-108px)]',
|
||||
],
|
||||
withTopHeader ? null : 'lg:min-h-screen'
|
||||
)}
|
||||
>
|
||||
<TableOfContents
|
||||
context={context}
|
||||
header={
|
||||
withTopHeader ? null : (
|
||||
<div
|
||||
className={tcls(
|
||||
'hidden',
|
||||
'pr-4',
|
||||
'lg:flex',
|
||||
'grow-0',
|
||||
'flex-wrap',
|
||||
'dark:shadow-light/1',
|
||||
'text-base/tight'
|
||||
)}
|
||||
>
|
||||
<HeaderLogo context={context} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
innerHeader={
|
||||
// displays the search button and/or the space dropdown in the ToC according to the header/variant settings. E.g if there is no header, the search button will be displayed in the ToC.
|
||||
<>
|
||||
{!withTopHeader && (
|
||||
<SearchContainer
|
||||
style={CustomizationSearchStyle.Subtle}
|
||||
isMultiVariants={siteSpaces.length > 1}
|
||||
spaceTitle={siteSpace.title}
|
||||
siteSpaceId={siteSpace.id}
|
||||
className="max-lg:hidden"
|
||||
viewport="desktop"
|
||||
/>
|
||||
)}
|
||||
{!withTopHeader && withSections && sections && (
|
||||
<SiteSectionList
|
||||
className={tcls('hidden', 'lg:block')}
|
||||
sections={encodeClientSiteSections(context, sections)}
|
||||
/>
|
||||
)}
|
||||
{isMultiVariants && !sections && (
|
||||
<SpacesDropdown
|
||||
context={context}
|
||||
siteSpace={siteSpace}
|
||||
siteSpaces={siteSpaces}
|
||||
className={tcls(
|
||||
'w-full',
|
||||
'page-no-toc:hidden',
|
||||
'page-no-toc:site-header-none:flex'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div className="flex min-w-0 flex-1 flex-col">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{withFooter ? <Footer context={context} /> : null}
|
||||
</SpaceLayoutServerContext>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user