) => {
const isExternalWithOrigin = isExternalLink(href, window.location.origin);
- // Only trigger navigation context for internal links without modifier keys (i.e. open in new tab).
- if (!isExternal && !event.ctrlKey && !event.metaKey) {
+ // Only trigger navigation context for internal links in the same window without modifier keys (i.e. open in new tab).
+ if (!isExternal && target !== '_blank' && !event.ctrlKey && !event.metaKey) {
onNavigationClick(href);
}
diff --git a/packages/gitbook/src/components/primitives/Tooltip.tsx b/packages/gitbook/src/components/primitives/Tooltip.tsx
index 56ea447d9..2a9b8d463 100644
--- a/packages/gitbook/src/components/primitives/Tooltip.tsx
+++ b/packages/gitbook/src/components/primitives/Tooltip.tsx
@@ -4,6 +4,12 @@ import { tcls } from '@/lib/tailwind';
import * as RadixTooltip from '@radix-ui/react-tooltip';
import { useState } from 'react';
+export type TooltipProps = {
+ rootProps?: RadixTooltip.TooltipProps;
+ triggerProps?: RadixTooltip.TooltipTriggerProps;
+ contentProps?: RadixTooltip.TooltipContentProps;
+};
+
export function Tooltip(props: {
children: React.ReactNode;
label?: string | React.ReactNode;
diff --git a/packages/gitbook/src/lib/context.ts b/packages/gitbook/src/lib/context.ts
index 320c8e7c8..bfb5f0fb8 100644
--- a/packages/gitbook/src/lib/context.ts
+++ b/packages/gitbook/src/lib/context.ts
@@ -120,9 +120,15 @@ export type GitBookSiteContext = GitBookSpaceContext & {
/** All site spaces in the current section / or entire site */
siteSpaces: SiteSpace[];
+ /** Site spaces that are not hidden (visible to visitors). */
+ visibleSiteSpaces: SiteSpace[];
+
/** Sections of the site. */
sections: null | SiteSections;
+ /** Sections filtered to visible site spaces only. */
+ visibleSections: null | SiteSections;
+
/** Customizations of the site. */
customization: SiteCustomizationSettings;
@@ -261,9 +267,16 @@ export async function fetchSiteContextByIds(
const sections = ids.siteSection
? parseSiteSectionsAndGroups(siteStructure, ids.siteSection)
: null;
+ const visibleSections = ids.siteSection
+ ? parseVisibleSiteSectionsAndGroups(siteStructure, ids.siteSection)
+ : null;
// Parse the current siteSpace and siteSpaces based on the site structure type.
- const { siteSpaces, siteSpace }: { siteSpaces: SiteSpace[]; siteSpace: SiteSpace } = (() => {
+ const {
+ siteSpaces,
+ siteSpace,
+ visibleSiteSpaces,
+ }: { siteSpaces: SiteSpace[]; siteSpace: SiteSpace; visibleSiteSpaces: SiteSpace[] } = (() => {
if (siteStructure.type === 'siteSpaces') {
const siteSpaces = siteStructure.structure;
const siteSpace = siteSpaces.find((siteSpace) => siteSpace.id === ids.siteSpace);
@@ -274,7 +287,7 @@ export async function fetchSiteContextByIds(
);
}
- return { siteSpaces: filterHiddenSiteSpaces(siteSpaces), siteSpace };
+ return { siteSpaces, siteSpace, visibleSiteSpaces: filterHiddenSiteSpaces(siteSpaces) };
}
if (siteStructure.type === 'sections') {
@@ -295,7 +308,11 @@ export async function fetchSiteContextByIds(
);
}
- return { siteSpaces: filterHiddenSiteSpaces(siteSpaces), siteSpace };
+ return {
+ siteSpaces,
+ siteSpace,
+ visibleSiteSpaces: filterHiddenSiteSpaces(siteSpaces),
+ };
}
// @ts-expect-error
@@ -327,10 +344,12 @@ export async function fetchSiteContextByIds(
organizationId: ids.organization,
site,
siteSpaces,
+ visibleSiteSpaces,
siteSpace,
customization,
structure: siteStructure,
sections,
+ visibleSections,
scripts,
contextId: ids.contextId,
isFallback: ids.isFallback,
@@ -434,13 +453,56 @@ function filterHiddenSiteSpaces(siteSpaces: SiteSpace[]): SiteSpace[] {
}
function parseSiteSectionsAndGroups(structure: SiteStructure, siteSectionId: string) {
- const sectionsAndGroups = getSiteStructureSections(structure, { ignoreGroups: false });
+ const sectionsAndGroups = getSiteStructureSections(structure);
const section = parseCurrentSection(structure, siteSectionId);
assert(section, `couldn't find section "${siteSectionId}" in site structure`);
return { list: sectionsAndGroups, current: section } satisfies SiteSections;
}
+function parseVisibleSiteSectionsAndGroups(structure: SiteStructure, siteSectionId: string) {
+ const { list: sectionsAndGroups, current: section } = parseSiteSectionsAndGroups(
+ structure,
+ siteSectionId
+ );
+ const visibleSectionsAndGroups = filterSectionsAndGroupsWithHiddenSiteSpaces(sectionsAndGroups);
+ return { list: visibleSectionsAndGroups, current: section } satisfies SiteSections;
+}
+
function parseCurrentSection(structure: SiteStructure, siteSectionId: string) {
const sections = getSiteStructureSections(structure, { ignoreGroups: true });
return sections.find((section) => section.id === siteSectionId);
}
+
+type SectionOrGroup = SiteSection | SiteSectionGroup;
+
+/**
+ * Filter out sections where all site spaces are hidden and groups that become empty after filtering.
+ */
+function filterSectionsAndGroupsWithHiddenSiteSpaces(
+ sectionsOrGroups: SectionOrGroup[]
+): SectionOrGroup[] {
+ return sectionsOrGroups
+ .map((entry) => {
+ if (entry.object === 'site-section') {
+ return sectionHasOnlyHiddenSiteSpaces(entry) ? null : entry;
+ }
+
+ const visibleChildren: SectionOrGroup[] = filterSectionsAndGroupsWithHiddenSiteSpaces(
+ entry.children
+ );
+
+ if (visibleChildren.length === 0) {
+ return null;
+ }
+
+ return {
+ ...entry,
+ children: visibleChildren,
+ };
+ })
+ .filter((entry): entry is SiteSection | SiteSectionGroup => Boolean(entry));
+}
+
+function sectionHasOnlyHiddenSiteSpaces(section: SiteSection) {
+ return section.siteSpaces.every((siteSpace) => siteSpace.hidden);
+}
diff --git a/packages/gitbook/src/lib/embeddable.ts b/packages/gitbook/src/lib/embeddable.ts
index 339d806ad..84d743544 100644
--- a/packages/gitbook/src/lib/embeddable.ts
+++ b/packages/gitbook/src/lib/embeddable.ts
@@ -55,5 +55,16 @@ export function getEmbeddableLinker(linker: GitBookLinker): GitBookLinker {
spaceBasePath: joinPath(override.spaceBasePath, '~gitbook/embed/page'),
});
},
+
+ toLinkForContent(rawURL: string): string {
+ const result = linker.toLinkForContent(rawURL);
+ // If the link is not relative or already an embed, return it as is
+ if (result.includes('~gitbook/embed') || !result.startsWith('/')) {
+ return result;
+ }
+
+ // If the link is relative, assume it's a section link and append the embed path
+ return joinPath(result, '~gitbook/embed/page');
+ },
};
}
diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx
index a0051ead9..f5245b689 100644
--- a/packages/gitbook/src/lib/references.tsx
+++ b/packages/gitbook/src/lib/references.tsx
@@ -129,7 +129,7 @@ export async function resolveContentRef(
const page = resolvePageResult?.page;
const ancestors =
resolvePageResult?.ancestors.map((ancestor) => ({
- label: ancestor.title,
+ label: ancestor.linkTitle || ancestor.title,
icon:
ancestor.emoji || ancestor.icon ? (
,
href,
});
@@ -177,7 +177,7 @@ export async function resolveContentRef(
parentPage && contentRef.page === parentPage.id && parentPage.type === 'group'
? parentPage
: page;
- text = pageOrGroup.title;
+ text = pageOrGroup.linkTitle || pageOrGroup.title;
emoji = isCurrentPage ? undefined : page.emoji;
icon =
;
}
diff --git a/packages/gitbook/src/middleware.ts b/packages/gitbook/src/middleware.ts
index 979276e22..f5b7f1113 100644
--- a/packages/gitbook/src/middleware.ts
+++ b/packages/gitbook/src/middleware.ts
@@ -612,6 +612,7 @@ function encodePathInSiteContent(rawPathname: string): {
}
switch (pathname) {
+ case '~gitbook/embed':
case '~gitbook/embed/assistant':
case '~gitbook/icon':
return { pathname };