diff --git a/aio/src/app/shared/toc.service.spec.ts b/aio/src/app/shared/toc.service.spec.ts
index 30bb3ea03f..f4962d9086 100644
--- a/aio/src/app/shared/toc.service.spec.ts
+++ b/aio/src/app/shared/toc.service.spec.ts
@@ -299,12 +299,11 @@ describe('TocService', () => {
beforeEach(() => {
docId = 'fizz/buzz/';
- // An almost-actual
... with extra whitespace
callGenToc(`
Setup to develop locally.
`, docId);
diff --git a/aio/src/app/shared/toc.service.ts b/aio/src/app/shared/toc.service.ts
index 91f5390cf8..fcbcdff11a 100644
--- a/aio/src/app/shared/toc.service.ts
+++ b/aio/src/app/shared/toc.service.ts
@@ -34,12 +34,16 @@ export class TocService {
const headings = this.findTocHeadings(docElement);
const idMap = new Map();
- const tocList = headings.map(heading => ({
- content: this.extractHeadingSafeHtml(heading),
- href: `${docId}#${this.getId(heading, idMap)}`,
- level: heading.tagName.toLowerCase(),
- title: (heading.textContent || '').trim(),
- }));
+ const tocList = headings.map(heading => {
+ const {title, content} = this.extractHeadingSafeHtml(heading);
+
+ return {
+ level: heading.tagName.toLowerCase(),
+ href: `${docId}#${this.getId(heading, idMap)}`,
+ title,
+ content,
+ };
+ });
this.tocList.next(tocList);
@@ -52,29 +56,35 @@ export class TocService {
this.tocList.next([]);
}
- // This bad boy exists only to strip off the anchor link attached to a heading
+ // Transform the HTML content to be safe to use in the ToC:
+ // - Strip off the auto-generated heading anchor links).
+ // - Strip off any anchor links (but keep their content)
+ // - Mark the HTML as trusted to be used with `[innerHTML]`.
private extractHeadingSafeHtml(heading: HTMLHeadingElement) {
const div: HTMLDivElement = this.document.createElement('div');
div.innerHTML = heading.innerHTML;
- const anchorLinks = Array.from(div.querySelectorAll('a'));
- for (const anchorLink of anchorLinks) {
- if (!anchorLink.classList.contains('header-link')) {
- // this is an anchor that contains actual content that we want to keep
- // move the contents of the anchor into its parent
- const parent = anchorLink.parentNode!;
- while (anchorLink.childNodes.length) {
- parent.insertBefore(anchorLink.childNodes[0], anchorLink);
- }
+
+ // Remove any `.header-link` elements (along with their content).
+ div.querySelectorAll('.header-link').forEach(removeNode);
+
+ // Remove any remaining `a` elements (but keep their content).
+ div.querySelectorAll('a').forEach(anchorLink => {
+ // We want to keep the content of this anchor, so move it into its parent.
+ const parent = anchorLink.parentNode!;
+ while (anchorLink.childNodes.length) {
+ parent.insertBefore(anchorLink.childNodes[0], anchorLink);
}
- // now remove the anchor
- if (anchorLink.parentNode !== null) {
- // We cannot use ChildNode.remove() because of IE11
- anchorLink.parentNode.removeChild(anchorLink);
- }
- }
- // security: the document element which provides this heading content
- // is always authored by the documentation team and is considered to be safe
- return this.domSanitizer.bypassSecurityTrustHtml(div.innerHTML.trim());
+
+ // Now, remove the anchor.
+ removeNode(anchorLink);
+ });
+
+ return {
+ // Security: The document element which provides this heading content is always authored by
+ // the documentation team and is considered to be safe.
+ content: this.domSanitizer.bypassSecurityTrustHtml(div.innerHTML.trim()),
+ title: (div.textContent || '').trim(),
+ };
}
private findTocHeadings(docElement: Element): HTMLHeadingElement[] {
@@ -115,3 +125,11 @@ export class TocService {
}
}
}
+
+// Helpers
+function removeNode(node: Node): void {
+ if (node.parentNode !== null) {
+ // We cannot use `Node.remove()` because of IE11.
+ node.parentNode.removeChild(node);
+ }
+}