mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-10-08 14:20:55 +00:00
Keep the embed open when switching section from the breadcrumbs (RND-12559) (#4670)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Keep the docs embed open when switching section, variant or language.
|
||||
@@ -97,6 +97,7 @@ export async function EmbeddableDocsPage(
|
||||
) ?? context.siteSpace
|
||||
}
|
||||
siteSpaces={variants.translations}
|
||||
asEmbeddable
|
||||
className="my-1.5 ml-2 self-start"
|
||||
/>
|
||||
) : null}
|
||||
@@ -125,6 +126,7 @@ export async function EmbeddableDocsPage(
|
||||
) ?? context.siteSpace
|
||||
}
|
||||
siteSpaces={variants.translations}
|
||||
asEmbeddable
|
||||
className="max-md:[&_.button-content]:block"
|
||||
/>
|
||||
) : null
|
||||
@@ -137,6 +139,7 @@ export async function EmbeddableDocsPage(
|
||||
context={context}
|
||||
siteSpace={context.siteSpace}
|
||||
siteSpaces={variants.generic}
|
||||
asEmbeddable
|
||||
className="w-full px-3"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -2,9 +2,10 @@ import type { SiteSpace } from '@gitbook/api';
|
||||
import type { IconName } from '@gitbook/icons';
|
||||
|
||||
import type { ButtonProps } from '../primitives';
|
||||
import { getTargetURLForSiteSpace } from '../SiteSections/encodeClientSiteSections';
|
||||
import { SpacesDropdownClient } from './SpacesDropdownClient';
|
||||
import type { GitBookSiteContext } from '@/lib/context';
|
||||
import { getLocalizedTitle, getSiteSpaceURL } from '@/lib/sites';
|
||||
import { getLocalizedTitle } from '@/lib/sites';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
// Memoized regex for checking if a string starts with an emoji
|
||||
@@ -20,9 +21,18 @@ export function SpacesDropdown(
|
||||
siteSpace: SiteSpace;
|
||||
siteSpaces: SiteSpace[];
|
||||
className?: string;
|
||||
/** Keep the links inside the embed rather than sending readers to the full site. */
|
||||
asEmbeddable?: boolean;
|
||||
} & ButtonProps
|
||||
) {
|
||||
const { context, siteSpace, siteSpaces, className, ...buttonProps } = props;
|
||||
const {
|
||||
context,
|
||||
siteSpace,
|
||||
siteSpaces,
|
||||
className,
|
||||
asEmbeddable = false,
|
||||
...buttonProps
|
||||
} = props;
|
||||
const currentLanguage = context.locale;
|
||||
|
||||
const dropdownClassName = tcls(
|
||||
@@ -33,7 +43,7 @@ export function SpacesDropdown(
|
||||
const slimSpaces = siteSpaces.map((siteSp) => ({
|
||||
id: siteSp.id,
|
||||
title: getLocalizedTitle(siteSp, currentLanguage),
|
||||
url: getSiteSpaceURL(context, siteSp),
|
||||
url: getTargetURLForSiteSpace(context, siteSp, asEmbeddable),
|
||||
isActive: siteSp.id === siteSpace.id,
|
||||
spaceId: siteSp.space.id,
|
||||
}));
|
||||
@@ -57,9 +67,10 @@ export function TranslationsDropdown(
|
||||
siteSpace: SiteSpace;
|
||||
siteSpaces: SiteSpace[];
|
||||
className?: string;
|
||||
asEmbeddable?: boolean;
|
||||
} & ButtonProps
|
||||
) {
|
||||
const { context, siteSpace, siteSpaces, className, ...buttonProps } = props;
|
||||
const { context, siteSpace, siteSpaces, className, asEmbeddable, ...buttonProps } = props;
|
||||
|
||||
const title = getLocalizedTitle(siteSpace, context.locale);
|
||||
const hasEmojiPrefix = startsWithEmoji(title);
|
||||
@@ -70,6 +81,7 @@ export function TranslationsDropdown(
|
||||
context={context}
|
||||
siteSpace={siteSpace}
|
||||
siteSpaces={siteSpaces}
|
||||
asEmbeddable={asEmbeddable}
|
||||
variant="blank"
|
||||
className={tcls(
|
||||
'bg-transparent lg:max-w-64 max-md:[&_.button-content]:hidden',
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { JSONDocument, RevisionPageDocument, SiteInsightsDisplayContext } from '@gitbook/api';
|
||||
import {
|
||||
type JSONDocument,
|
||||
type RevisionPageDocument,
|
||||
SiteInsightsDisplayContext,
|
||||
} from '@gitbook/api';
|
||||
|
||||
import { DocumentView, DocumentViewSkeleton } from '../DocumentView';
|
||||
import { CurrentPageProvider } from '../hooks/useCurrentPage';
|
||||
@@ -127,6 +131,7 @@ export async function PageBody(props: {
|
||||
ancestors={ancestors}
|
||||
withRSSFeed={contentHasUpdates}
|
||||
hasAPIBlocks={hasAPIBlocks}
|
||||
asEmbeddable={insightsDisplayContext === SiteInsightsDisplayContext.Embed}
|
||||
/>
|
||||
{document && !isNodeEmpty(document) ? (
|
||||
<OptionalSuspense
|
||||
|
||||
@@ -19,14 +19,17 @@ import {
|
||||
import { PageAsideToggleButton } from '../PageAside/PageAsideButton';
|
||||
import { PageIcon } from '../PageIcon';
|
||||
import { getPDFURLSearchParams } from '../PDF';
|
||||
import { findBestTargetURL } from '../SiteSections/encodeClientSiteSections';
|
||||
import {
|
||||
findBestTargetURL,
|
||||
getTargetURLForSiteSpace,
|
||||
} from '../SiteSections/encodeClientSiteSections';
|
||||
import { categorizeVariants } from '../SpaceLayout/categorizeVariants';
|
||||
import { BreadcrumbItemDropdown, type BreadcrumbSibling } from './BreadcrumbItemDropdown';
|
||||
import { PageTags } from './PageTags';
|
||||
import type { GitBookSiteContext, SiteStructureNode } from '@/lib/context';
|
||||
import { hasAdaptiveMcpEndpoint } from '@/lib/mcp/endpoints';
|
||||
import { type AncestorRevisionPage, resolveFirstDocument } from '@/lib/pages';
|
||||
import { getLocalizedTitle, getSiteSpaceURL } from '@/lib/sites';
|
||||
import { getLocalizedTitle } from '@/lib/sites';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { getPageRSSURL } from '@/routes/rss';
|
||||
|
||||
@@ -41,8 +44,10 @@ export async function PageHeader(props: {
|
||||
* exactly as-is.
|
||||
*/
|
||||
hasAPIBlocks: boolean;
|
||||
/** Keep the context crumbs' links inside the embed rather than sending readers to the full site. */
|
||||
asEmbeddable: boolean;
|
||||
}) {
|
||||
const { context, page, ancestors, withRSSFeed, hasAPIBlocks } = props;
|
||||
const { context, page, ancestors, withRSSFeed, hasAPIBlocks, asEmbeddable } = props;
|
||||
const { revision, linker } = context;
|
||||
|
||||
const hasAncestors = ancestors.length > 0;
|
||||
@@ -82,7 +87,10 @@ export async function PageHeader(props: {
|
||||
// Section groups have no URL of their own; only sections are navigable. Resolve the
|
||||
// section to the site space matching the current variant so switching sections keeps
|
||||
// the reader on their current variant (same logic as the section tabs).
|
||||
href: node.object === 'site-section' ? findBestTargetURL(context, node) : undefined,
|
||||
href:
|
||||
node.object === 'site-section'
|
||||
? findBestTargetURL(context, node, asEmbeddable)
|
||||
: undefined,
|
||||
label: getLocalizedTitle(node, context.locale),
|
||||
icon: node.icon,
|
||||
siblings: siblings
|
||||
@@ -92,7 +100,7 @@ export async function PageHeader(props: {
|
||||
// Don't offer hidden sections/groups as switch targets.
|
||||
.filter((sibling) => !visibleSectionIds || visibleSectionIds.has(sibling.id))
|
||||
.map((sibling) => {
|
||||
const href = getSectionNodeURL(context, sibling);
|
||||
const href = getSectionNodeURL(context, sibling, asEmbeddable);
|
||||
// Keep siblings whose URL is "" (the site's first page); only drop the ones
|
||||
// with no resolvable URL at all.
|
||||
return href !== undefined
|
||||
@@ -112,7 +120,7 @@ export async function PageHeader(props: {
|
||||
if (currentSiteSpace) {
|
||||
contextCrumbs.push({
|
||||
key: `variant-${currentSiteSpace.id}`,
|
||||
href: getSiteSpaceURL(context, currentSiteSpace),
|
||||
href: getTargetURLForSiteSpace(context, currentSiteSpace, asEmbeddable),
|
||||
label: getLocalizedTitle(currentSiteSpace, context.locale),
|
||||
siblings: [],
|
||||
// Reuse the header's variant switcher rather than plain per-variant links, so each entry
|
||||
@@ -122,7 +130,7 @@ export async function PageHeader(props: {
|
||||
slimSpaces: variantSpaces.map((siteSpace) => ({
|
||||
id: siteSpace.id,
|
||||
title: getLocalizedTitle(siteSpace, context.locale),
|
||||
url: getSiteSpaceURL(context, siteSpace),
|
||||
url: getTargetURLForSiteSpace(context, siteSpace, asEmbeddable),
|
||||
isActive: siteSpace.id === currentSiteSpace.id,
|
||||
spaceId: siteSpace.space.id,
|
||||
})),
|
||||
@@ -448,9 +456,13 @@ function collectSectionNodeIds(list: SiteStructureNode[]): Set<string> {
|
||||
* The URL resolves to the site space matching the current variant (same logic as the section tabs),
|
||||
* so switching sections keeps the reader on their current variant.
|
||||
*/
|
||||
function getSectionNodeURL(context: GitBookSiteContext, node: SectionNode): string | undefined {
|
||||
function getSectionNodeURL(
|
||||
context: GitBookSiteContext,
|
||||
node: SectionNode,
|
||||
asEmbeddable: boolean
|
||||
): string | undefined {
|
||||
const section = findFirstSection(node);
|
||||
return section ? findBestTargetURL(context, section) : undefined;
|
||||
return section ? findBestTargetURL(context, section, asEmbeddable) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -284,11 +284,11 @@ export async function getSitePageData(props: SitePageProps) {
|
||||
if (pathname !== null) {
|
||||
// If the pathname was not normalized, redirect to the normalized version
|
||||
// before trying to resolve the page again
|
||||
redirect(context.linker.toPathInSpace(pathname));
|
||||
redirect(context.linker.toPathForPagePath({ path: pathname }));
|
||||
} else {
|
||||
// If the page is not found and we are in fallback mode, return a redirect to the basepath
|
||||
if (context.isFallback) {
|
||||
redirect(context.linker.toPathInSpace('/'));
|
||||
redirect(context.linker.toPathForPagePath({ path: '/' }));
|
||||
}
|
||||
notFound();
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ function getTargetURLForSection(
|
||||
return getSectionURL(context, section);
|
||||
}
|
||||
|
||||
function getTargetURLForSiteSpace(
|
||||
export function getTargetURLForSiteSpace(
|
||||
context: GitBookSiteContext,
|
||||
siteSpace: SiteSpace,
|
||||
asEmbeddable: boolean
|
||||
|
||||
Reference in New Issue
Block a user