Skip to content

Commit cb35f45

Browse files
committed
feat: enhance Table of Contents functionality by excluding headerActions from extracted text
1 parent bb3d343 commit cb35f45

File tree

1 file changed

+40
-4
lines changed
  • packages/documentation/common/table-of-contents

1 file changed

+40
-4
lines changed

packages/documentation/common/table-of-contents/index.tsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,39 @@ export function TableOfContents({
9696
const headingSelector = headingLevels.join(', ');
9797
const headingElements = container.querySelectorAll<HTMLHeadingElement>(headingSelector);
9898

99+
// Helper function to extract text excluding headerActions
100+
const getHeadingText = (heading: HTMLHeadingElement): string => {
101+
// Clone the heading to avoid modifying the original
102+
const clone = heading.cloneNode(true) as HTMLHeadingElement;
103+
104+
// Remove all interactive elements first (buttons, switches, etc.)
105+
const interactiveElements = clone.querySelectorAll('button, [role="switch"], [role="button"], input[type="checkbox"]');
106+
interactiveElements.forEach((el) => el.remove());
107+
108+
// Remove any elements that contain "Show Code" or "Show Preview" text
109+
// Process from deepest to shallowest to handle nested structures correctly
110+
const allElements = Array.from(clone.querySelectorAll('*')).reverse();
111+
allElements.forEach((el) => {
112+
const text = el.textContent?.trim() || '';
113+
// Check if element's text matches button labels exactly or starts with them
114+
// This avoids false positives from headings that might contain words like "Code"
115+
if (text === 'Show Code' || text === 'Show Preview' ||
116+
text === 'Hide Code' || text === 'Hide Preview' ||
117+
text.startsWith('Show Code') || text.startsWith('Show Preview') ||
118+
text.startsWith('Hide Code') || text.startsWith('Hide Preview')) {
119+
// Remove this element and check if parent should also be removed
120+
const parent = el.parentElement;
121+
el.remove();
122+
// If parent is now empty or only contains whitespace, remove it too
123+
if (parent && parent.textContent?.trim() === '') {
124+
parent.remove();
125+
}
126+
}
127+
});
128+
129+
return clone.textContent?.trim() || '';
130+
};
131+
99132
const extractedHeadings: Heading[] = Array.from(headingElements)
100133
.filter(heading => {
101134
// Only include actual h2/h3 HTML elements (not divs with role="heading")
@@ -105,7 +138,8 @@ export function TableOfContents({
105138
}
106139

107140
// Filter out empty headings or headings with only whitespace
108-
const text = heading.textContent?.trim();
141+
// Use getHeadingText to exclude headerActions
142+
const text = getHeadingText(heading);
109143
if (!text || text.length === 0) {
110144
return false;
111145
}
@@ -149,10 +183,12 @@ export function TableOfContents({
149183
return true;
150184
})
151185
.map((heading, index) => {
186+
// Extract text excluding headerActions using the helper function
187+
const headingText = getHeadingText(heading);
188+
152189
// Generate ID if it doesn't exist
153190
if (!heading.id) {
154-
const text = heading.textContent || '';
155-
const id = text
191+
const id = headingText
156192
.toLowerCase()
157193
.replace(/[^a-z0-9]+/g, '-')
158194
.replace(/^-|-$/g, '');
@@ -161,7 +197,7 @@ export function TableOfContents({
161197

162198
return {
163199
id: heading.id,
164-
text: heading.textContent?.trim() || '',
200+
text: headingText,
165201
level: parseInt(heading.tagName[1] || '2', 10),
166202
};
167203
});

0 commit comments

Comments
 (0)