Skip to content

Commit b49caaf

Browse files
authored
Support for hidden sections (#3831)
1 parent f372eec commit b49caaf

File tree

6 files changed

+97
-27
lines changed

6 files changed

+97
-27
lines changed

packages/gitbook/src/components/Header/Header.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ export function Header(props: {
2525
};
2626
}) {
2727
const { context, withTopHeader, variants } = props;
28-
const { siteSpace, siteSpaces, sections, customization } = context;
28+
const { siteSpace, visibleSiteSpaces, visibleSections, customization } = context;
2929

3030
const withSections = Boolean(
31-
sections &&
32-
(sections.list.length > 1 || // Show section tabs if there are at least 2 sections or at least 1 section group
33-
sections.list.some((s) => s.object === 'site-section-group'))
31+
visibleSections &&
32+
(visibleSections.list.length > 1 || // Show section tabs if there are at least 2 sections or at least 1 section group
33+
visibleSections.list.some((s) => s.object === 'site-section-group'))
3434
);
3535

3636
return (
@@ -139,20 +139,22 @@ export function Header(props: {
139139
style={customization.styling.search}
140140
withVariants={variants.generic.length > 1}
141141
withSiteVariants={
142-
sections?.list.some(
142+
visibleSections?.list.some(
143143
(s) =>
144144
s.object === 'site-section' && s.siteSpaces.length > 1
145145
) ?? false
146146
}
147-
withSections={sections ? sections.list.length > 1 : false}
147+
withSections={
148+
visibleSections ? visibleSections.list.length > 1 : false
149+
}
148150
section={
149-
sections
150-
? // Client-encode to avoid a serialisation issue that was causing the language selector to disappear
151-
encodeClientSiteSections(context, sections).current
151+
visibleSections
152+
? // Client-encode to avoid a serialization issue that was causing the language selector to disappear
153+
encodeClientSiteSections(context, visibleSections).current
152154
: undefined
153155
}
154156
siteSpace={siteSpace}
155-
siteSpaces={siteSpaces}
157+
siteSpaces={visibleSiteSpaces}
156158
viewport={!withTopHeader ? 'mobile' : undefined}
157159
/>
158160
</div>
@@ -196,9 +198,9 @@ export function Header(props: {
196198
</div>
197199
</div>
198200

199-
{sections && withSections ? (
201+
{visibleSections && withSections ? (
200202
<div className="transition-[padding] duration-300 lg:chat-open:pr-80 xl:chat-open:pr-96">
201-
<SiteSectionTabs sections={encodeClientSiteSections(context, sections)}>
203+
<SiteSectionTabs sections={encodeClientSiteSections(context, visibleSections)}>
202204
{variants.translations.length > 1 ? (
203205
<TranslationsDropdown
204206
context={context}

packages/gitbook/src/components/SitePage/SitePage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ export async function generateSitePageViewport(context: GitBookSiteContext): Pro
123123
* A string concatenation of the site structure (sections and variants) titles.
124124
*/
125125
function getSiteStructureTitle(context: GitBookSiteContext): string | null {
126-
const { sections, siteSpace, siteSpaces } = context;
126+
const { visibleSections: sections, siteSpace, visibleSiteSpaces: siteSpaces } = context;
127127

128128
const title = [];
129129
if (
@@ -259,7 +259,7 @@ export async function getSitePageData(props: SitePageProps) {
259259
);
260260
}
261261

262-
const { customization, sections } = context;
262+
const { customization, visibleSections } = context;
263263
const { page, ancestors } = pageTarget;
264264

265265
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
@@ -270,7 +270,7 @@ export async function getSitePageData(props: SitePageProps) {
270270
);
271271
const withPageFeedback = customization.feedback.enabled;
272272

273-
const withSections = Boolean(sections && sections.list.length > 0);
273+
const withSections = Boolean(visibleSections && visibleSections.list.length > 0);
274274

275275
const document = await getPageDocument(context, page);
276276

packages/gitbook/src/components/SpaceLayout/SpaceLayout.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ function makeContext(current: FakeSiteSpace, all: FakeSiteSpace[]) {
1414
// Only the properties used by categorizeVariants are required for these tests
1515
siteSpace: current,
1616
siteSpaces: all,
17+
visibleSiteSpaces: all,
1718
} as unknown as Parameters<typeof categorizeVariants>[0];
1819
}
1920

packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ export function SpaceLayoutServerContext(props: SpaceLayoutProps) {
9999
*/
100100
export function SpaceLayout(props: SpaceLayoutProps) {
101101
const { context, children } = props;
102-
const { siteSpace, customization, sections, siteSpaces } = context;
102+
const { siteSpace, customization, visibleSections, visibleSiteSpaces } = context;
103103

104104
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
105105

106-
const withSections = Boolean(sections && sections.list.length > 1);
106+
const withSections = Boolean(visibleSections && visibleSections.list.length > 1);
107107
const variants = categorizeVariants(context);
108108

109109
const withFooter =
@@ -181,25 +181,28 @@ export function SpaceLayout(props: SpaceLayoutProps) {
181181
style={CustomizationSearchStyle.Subtle}
182182
withVariants={variants.generic.length > 1}
183183
withSiteVariants={
184-
sections?.list.some(
184+
visibleSections?.list.some(
185185
(s) =>
186186
s.object === 'site-section' &&
187187
s.siteSpaces.length > 1
188188
) ?? false
189189
}
190190
withSections={withSections}
191-
section={sections?.current}
191+
section={visibleSections?.current}
192192
siteSpace={siteSpace}
193-
siteSpaces={siteSpaces}
193+
siteSpaces={visibleSiteSpaces}
194194
className="max-lg:hidden"
195195
viewport="desktop"
196196
/>
197197
</div>
198198
)}
199-
{!withTopHeader && withSections && sections && (
199+
{!withTopHeader && withSections && visibleSections && (
200200
<SiteSectionList
201201
className={tcls('hidden', 'lg:block')}
202-
sections={encodeClientSiteSections(context, sections)}
202+
sections={encodeClientSiteSections(
203+
context,
204+
visibleSections
205+
)}
203206
/>
204207
)}
205208
{variants.generic.length > 1 ? (

packages/gitbook/src/components/SpaceLayout/categorizeVariants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { GitBookSiteContext } from '@/lib/context';
55
* Categorize the variants of the space into generic and translation variants.
66
*/
77
export function categorizeVariants(context: GitBookSiteContext) {
8-
const { siteSpace, siteSpaces } = context;
8+
const { siteSpace, visibleSiteSpaces: siteSpaces } = context;
99
const currentLanguage = siteSpace.space.language;
1010

1111
// Get all languages of the variants.

packages/gitbook/src/lib/context.ts

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,15 @@ export type GitBookSiteContext = GitBookSpaceContext & {
120120
/** All site spaces in the current section / or entire site */
121121
siteSpaces: SiteSpace[];
122122

123+
/** Site spaces that are not hidden (visible to visitors). */
124+
visibleSiteSpaces: SiteSpace[];
125+
123126
/** Sections of the site. */
124127
sections: null | SiteSections;
125128

129+
/** Sections filtered to visible site spaces only. */
130+
visibleSections: null | SiteSections;
131+
126132
/** Customizations of the site. */
127133
customization: SiteCustomizationSettings;
128134

@@ -261,9 +267,16 @@ export async function fetchSiteContextByIds(
261267
const sections = ids.siteSection
262268
? parseSiteSectionsAndGroups(siteStructure, ids.siteSection)
263269
: null;
270+
const visibleSections = ids.siteSection
271+
? parseVisibleSiteSectionsAndGroups(siteStructure, ids.siteSection)
272+
: null;
264273

265274
// Parse the current siteSpace and siteSpaces based on the site structure type.
266-
const { siteSpaces, siteSpace }: { siteSpaces: SiteSpace[]; siteSpace: SiteSpace } = (() => {
275+
const {
276+
siteSpaces,
277+
siteSpace,
278+
visibleSiteSpaces,
279+
}: { siteSpaces: SiteSpace[]; siteSpace: SiteSpace; visibleSiteSpaces: SiteSpace[] } = (() => {
267280
if (siteStructure.type === 'siteSpaces') {
268281
const siteSpaces = siteStructure.structure;
269282
const siteSpace = siteSpaces.find((siteSpace) => siteSpace.id === ids.siteSpace);
@@ -274,7 +287,7 @@ export async function fetchSiteContextByIds(
274287
);
275288
}
276289

277-
return { siteSpaces: filterHiddenSiteSpaces(siteSpaces), siteSpace };
290+
return { siteSpaces, siteSpace, visibleSiteSpaces: filterHiddenSiteSpaces(siteSpaces) };
278291
}
279292

280293
if (siteStructure.type === 'sections') {
@@ -295,7 +308,11 @@ export async function fetchSiteContextByIds(
295308
);
296309
}
297310

298-
return { siteSpaces: filterHiddenSiteSpaces(siteSpaces), siteSpace };
311+
return {
312+
siteSpaces,
313+
siteSpace,
314+
visibleSiteSpaces: filterHiddenSiteSpaces(siteSpaces),
315+
};
299316
}
300317

301318
// @ts-expect-error
@@ -327,10 +344,12 @@ export async function fetchSiteContextByIds(
327344
organizationId: ids.organization,
328345
site,
329346
siteSpaces,
347+
visibleSiteSpaces,
330348
siteSpace,
331349
customization,
332350
structure: siteStructure,
333351
sections,
352+
visibleSections,
334353
scripts,
335354
contextId: ids.contextId,
336355
isFallback: ids.isFallback,
@@ -434,13 +453,58 @@ function filterHiddenSiteSpaces(siteSpaces: SiteSpace[]): SiteSpace[] {
434453
}
435454

436455
function parseSiteSectionsAndGroups(structure: SiteStructure, siteSectionId: string) {
437-
const sectionsAndGroups = getSiteStructureSections(structure, { ignoreGroups: false });
456+
const sectionsAndGroups = getSiteStructureSections(structure);
438457
const section = parseCurrentSection(structure, siteSectionId);
439458
assert(section, `couldn't find section "${siteSectionId}" in site structure`);
440459
return { list: sectionsAndGroups, current: section } satisfies SiteSections;
441460
}
442461

462+
function parseVisibleSiteSectionsAndGroups(structure: SiteStructure, siteSectionId: string) {
463+
const { list: sectionsAndGroups, current: section } = parseSiteSectionsAndGroups(
464+
structure,
465+
siteSectionId
466+
);
467+
const visibleSectionsAndGroups = filterSectionsAndGroupsWithHiddenSiteSpaces(sectionsAndGroups);
468+
const current = section && !sectionHasOnlyHiddenSiteSpaces(section) ? section : null;
469+
assert(current, `couldn't find section "${siteSectionId}" in site structure`);
470+
return { list: visibleSectionsAndGroups, current } satisfies SiteSections;
471+
}
472+
443473
function parseCurrentSection(structure: SiteStructure, siteSectionId: string) {
444474
const sections = getSiteStructureSections(structure, { ignoreGroups: true });
445475
return sections.find((section) => section.id === siteSectionId);
446476
}
477+
478+
type SectionOrGroup = SiteSection | SiteSectionGroup;
479+
480+
/**
481+
* Filter out sections where all site spaces are hidden and groups that become empty after filtering.
482+
*/
483+
function filterSectionsAndGroupsWithHiddenSiteSpaces(
484+
sectionsOrGroups: SectionOrGroup[]
485+
): SectionOrGroup[] {
486+
return sectionsOrGroups
487+
.map((entry) => {
488+
if (entry.object === 'site-section') {
489+
return sectionHasOnlyHiddenSiteSpaces(entry) ? null : entry;
490+
}
491+
492+
const visibleChildren: SectionOrGroup[] = filterSectionsAndGroupsWithHiddenSiteSpaces(
493+
entry.children
494+
);
495+
496+
if (visibleChildren.length === 0) {
497+
return null;
498+
}
499+
500+
return {
501+
...entry,
502+
children: visibleChildren,
503+
};
504+
})
505+
.filter((entry): entry is SiteSection | SiteSectionGroup => Boolean(entry));
506+
}
507+
508+
function sectionHasOnlyHiddenSiteSpaces(section: SiteSection) {
509+
return section.siteSpaces.every((siteSpace) => siteSpace.hidden);
510+
}

0 commit comments

Comments
 (0)