diff --git a/aio/src/app/shared/toc.service.spec.ts b/aio/src/app/shared/toc.service.spec.ts
index 8b19fc9371..f3af3d071c 100644
--- a/aio/src/app/shared/toc.service.spec.ts
+++ b/aio/src/app/shared/toc.service.spec.ts
@@ -291,23 +291,20 @@ describe('TocService', () => {
});
});
- describe('TocItem for an h2 with anchor link and extra whitespace', () => {
+ describe('TocItem for an h2 with links and extra whitespace', () => {
let docId: string;
- let docEl: HTMLDivElement;
let tocItem: TocItem;
- let expectedTocContent: string;
beforeEach(() => {
docId = 'fizz/buzz/';
- expectedTocContent = 'Setup to develop locally.';
// An almost-actual
... with extra whitespace
- docEl = callGenToc(`
+ callGenToc(`
-
+ Setup to develop locally.
+
- ${expectedTocContent}
`, docId);
@@ -331,7 +328,7 @@ describe('TocService', () => {
it('should have bypassed HTML sanitizing of heading\'s innerHTML ', () => {
const domSanitizer: TestDomSanitizer = injector.get(DomSanitizer);
expect(domSanitizer.bypassSecurityTrustHtml)
- .toHaveBeenCalledWith(expectedTocContent);
+ .toHaveBeenCalledWith('Setup to develop locally.');
});
});
});
@@ -352,13 +349,20 @@ class TestDomSanitizer {
}
class MockScrollSpyService {
- $lastInfo: {
+ private $$lastInfo: {
active: Subject,
- unspy: jasmine.Spy
- };
+ unspy: jasmine.Spy,
+ } | undefined;
+
+ get $lastInfo() {
+ if (!this.$$lastInfo) {
+ throw new Error('$lastInfo is not yet defined. You must call `spyOn` first.');
+ }
+ return this.$$lastInfo;
+ }
spyOn(headings: HTMLHeadingElement[]): ScrollSpyInfo {
- return this.$lastInfo = {
+ return this.$$lastInfo = {
active: new Subject(),
unspy: jasmine.createSpy('unspy'),
};
diff --git a/aio/src/app/shared/toc.service.ts b/aio/src/app/shared/toc.service.ts
index a321f9b9fb..df5f50b94f 100644
--- a/aio/src/app/shared/toc.service.ts
+++ b/aio/src/app/shared/toc.service.ts
@@ -16,7 +16,7 @@ export interface TocItem {
export class TocService {
tocList = new ReplaySubject(1);
activeItemIndex = new ReplaySubject(1);
- private scrollSpyInfo: ScrollSpyInfo | null;
+ private scrollSpyInfo: ScrollSpyInfo | null = null;
constructor(
@Inject(DOCUMENT) private document: any,
@@ -53,15 +53,25 @@ export class TocService {
// This bad boy exists only to strip off the anchor link attached to a heading
private extractHeadingSafeHtml(heading: HTMLHeadingElement) {
- const a = this.document.createElement('a') as HTMLAnchorElement;
- a.innerHTML = heading.innerHTML;
- const anchorLink = a.querySelector('a');
- if (anchorLink) {
- a.removeChild(anchorLink);
+ const div: HTMLDivElement = this.document.createElement('div');
+ div.innerHTML = heading.innerHTML;
+ const anchorLinks: NodeListOf = div.querySelectorAll('a');
+ for (let i = 0; i < anchorLinks.length; i++) {
+ const anchorLink = anchorLinks[i];
+ 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);
+ }
+ }
+ // now remove the anchor
+ anchorLink.remove();
}
// 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(a.innerHTML.trim());
+ return this.domSanitizer.bypassSecurityTrustHtml(div.innerHTML.trim());
}
private findTocHeadings(docElement: Element): HTMLHeadingElement[] {