Support for hidden sections (#3831)

This commit is contained in:
Nolann B.
2025-12-01 14:48:48 +01:00
committed by GitHub
parent f372eec393
commit b49caafa9c
6 changed files with 97 additions and 27 deletions
@@ -25,12 +25,12 @@ export function Header(props: {
};
}) {
const { context, withTopHeader, variants } = props;
const { siteSpace, siteSpaces, sections, customization } = context;
const { siteSpace, visibleSiteSpaces, visibleSections, customization } = context;
const withSections = Boolean(
sections &&
(sections.list.length > 1 || // Show section tabs if there are at least 2 sections or at least 1 section group
sections.list.some((s) => s.object === 'site-section-group'))
visibleSections &&
(visibleSections.list.length > 1 || // Show section tabs if there are at least 2 sections or at least 1 section group
visibleSections.list.some((s) => s.object === 'site-section-group'))
);
return (
@@ -139,20 +139,22 @@ export function Header(props: {
style={customization.styling.search}
withVariants={variants.generic.length > 1}
withSiteVariants={
sections?.list.some(
visibleSections?.list.some(
(s) =>
s.object === 'site-section' && s.siteSpaces.length > 1
) ?? false
}
withSections={sections ? sections.list.length > 1 : false}
withSections={
visibleSections ? visibleSections.list.length > 1 : false
}
section={
sections
? // Client-encode to avoid a serialisation issue that was causing the language selector to disappear
encodeClientSiteSections(context, sections).current
visibleSections
? // Client-encode to avoid a serialization issue that was causing the language selector to disappear
encodeClientSiteSections(context, visibleSections).current
: undefined
}
siteSpace={siteSpace}
siteSpaces={siteSpaces}
siteSpaces={visibleSiteSpaces}
viewport={!withTopHeader ? 'mobile' : undefined}
/>
</div>
@@ -196,9 +198,9 @@ export function Header(props: {
</div>
</div>
{sections && withSections ? (
{visibleSections && withSections ? (
<div className="transition-[padding] duration-300 lg:chat-open:pr-80 xl:chat-open:pr-96">
<SiteSectionTabs sections={encodeClientSiteSections(context, sections)}>
<SiteSectionTabs sections={encodeClientSiteSections(context, visibleSections)}>
{variants.translations.length > 1 ? (
<TranslationsDropdown
context={context}
@@ -123,7 +123,7 @@ export async function generateSitePageViewport(context: GitBookSiteContext): Pro
* A string concatenation of the site structure (sections and variants) titles.
*/
function getSiteStructureTitle(context: GitBookSiteContext): string | null {
const { sections, siteSpace, siteSpaces } = context;
const { visibleSections: sections, siteSpace, visibleSiteSpaces: siteSpaces } = context;
const title = [];
if (
@@ -259,7 +259,7 @@ export async function getSitePageData(props: SitePageProps) {
);
}
const { customization, sections } = context;
const { customization, visibleSections } = context;
const { page, ancestors } = pageTarget;
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
@@ -270,7 +270,7 @@ export async function getSitePageData(props: SitePageProps) {
);
const withPageFeedback = customization.feedback.enabled;
const withSections = Boolean(sections && sections.list.length > 0);
const withSections = Boolean(visibleSections && visibleSections.list.length > 0);
const document = await getPageDocument(context, page);
@@ -14,6 +14,7 @@ function makeContext(current: FakeSiteSpace, all: FakeSiteSpace[]) {
// Only the properties used by categorizeVariants are required for these tests
siteSpace: current,
siteSpaces: all,
visibleSiteSpaces: all,
} as unknown as Parameters<typeof categorizeVariants>[0];
}
@@ -99,11 +99,11 @@ export function SpaceLayoutServerContext(props: SpaceLayoutProps) {
*/
export function SpaceLayout(props: SpaceLayoutProps) {
const { context, children } = props;
const { siteSpace, customization, sections, siteSpaces } = context;
const { siteSpace, customization, visibleSections, visibleSiteSpaces } = context;
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
const withSections = Boolean(sections && sections.list.length > 1);
const withSections = Boolean(visibleSections && visibleSections.list.length > 1);
const variants = categorizeVariants(context);
const withFooter =
@@ -181,25 +181,28 @@ export function SpaceLayout(props: SpaceLayoutProps) {
style={CustomizationSearchStyle.Subtle}
withVariants={variants.generic.length > 1}
withSiteVariants={
sections?.list.some(
visibleSections?.list.some(
(s) =>
s.object === 'site-section' &&
s.siteSpaces.length > 1
) ?? false
}
withSections={withSections}
section={sections?.current}
section={visibleSections?.current}
siteSpace={siteSpace}
siteSpaces={siteSpaces}
siteSpaces={visibleSiteSpaces}
className="max-lg:hidden"
viewport="desktop"
/>
</div>
)}
{!withTopHeader && withSections && sections && (
{!withTopHeader && withSections && visibleSections && (
<SiteSectionList
className={tcls('hidden', 'lg:block')}
sections={encodeClientSiteSections(context, sections)}
sections={encodeClientSiteSections(
context,
visibleSections
)}
/>
)}
{variants.generic.length > 1 ? (
@@ -5,7 +5,7 @@ import type { GitBookSiteContext } from '@/lib/context';
* Categorize the variants of the space into generic and translation variants.
*/
export function categorizeVariants(context: GitBookSiteContext) {
const { siteSpace, siteSpaces } = context;
const { siteSpace, visibleSiteSpaces: siteSpaces } = context;
const currentLanguage = siteSpace.space.language;
// Get all languages of the variants.
+68 -4
View File
@@ -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,58 @@ 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);
const current = section && !sectionHasOnlyHiddenSiteSpaces(section) ? section : null;
assert(current, `couldn't find section "${siteSectionId}" in site structure`);
return { list: visibleSectionsAndGroups, current } 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);
}