fix(aio): remove all links from toc titles (#22533)
The previous approach just removed the first `a` tag that was found, but now that the header-link anchor is not at the start of the heading, it could fail. Closes #22493 PR Close #22533
This commit is contained in:
parent
51ca643c27
commit
12be311618
|
@ -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 docId: string;
|
||||||
let docEl: HTMLDivElement;
|
|
||||||
let tocItem: TocItem;
|
let tocItem: TocItem;
|
||||||
let expectedTocContent: string;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
docId = 'fizz/buzz/';
|
docId = 'fizz/buzz/';
|
||||||
expectedTocContent = 'Setup to develop <i>locally</i>.';
|
|
||||||
|
|
||||||
// An almost-actual <h2> ... with extra whitespace
|
// An almost-actual <h2> ... with extra whitespace
|
||||||
docEl = callGenToc(`
|
callGenToc(`
|
||||||
<h2 id="setup-to-develop-locally">
|
<h2 id="setup-to-develop-locally">
|
||||||
<a href="tutorial/toh-pt1#setup-to-develop-locally" aria-hidden="true">
|
Setup to <a href="moo">develop</a> <i>locally</i>.
|
||||||
|
<a class="header-link" href="tutorial/toh-pt1#setup-to-develop-locally" aria-hidden="true">
|
||||||
<span class="icon icon-link"></span>
|
<span class="icon icon-link"></span>
|
||||||
</a>
|
</a>
|
||||||
${expectedTocContent}
|
|
||||||
</h2>
|
</h2>
|
||||||
`, docId);
|
`, docId);
|
||||||
|
|
||||||
|
@ -331,7 +328,7 @@ describe('TocService', () => {
|
||||||
it('should have bypassed HTML sanitizing of heading\'s innerHTML ', () => {
|
it('should have bypassed HTML sanitizing of heading\'s innerHTML ', () => {
|
||||||
const domSanitizer: TestDomSanitizer = injector.get(DomSanitizer);
|
const domSanitizer: TestDomSanitizer = injector.get(DomSanitizer);
|
||||||
expect(domSanitizer.bypassSecurityTrustHtml)
|
expect(domSanitizer.bypassSecurityTrustHtml)
|
||||||
.toHaveBeenCalledWith(expectedTocContent);
|
.toHaveBeenCalledWith('Setup to develop <i>locally</i>.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -352,13 +349,20 @@ class TestDomSanitizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockScrollSpyService {
|
class MockScrollSpyService {
|
||||||
$lastInfo: {
|
private $$lastInfo: {
|
||||||
active: Subject<ScrollItem | null>,
|
active: Subject<ScrollItem | null>,
|
||||||
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 {
|
spyOn(headings: HTMLHeadingElement[]): ScrollSpyInfo {
|
||||||
return this.$lastInfo = {
|
return this.$$lastInfo = {
|
||||||
active: new Subject<ScrollItem | null>(),
|
active: new Subject<ScrollItem | null>(),
|
||||||
unspy: jasmine.createSpy('unspy'),
|
unspy: jasmine.createSpy('unspy'),
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,7 +16,7 @@ export interface TocItem {
|
||||||
export class TocService {
|
export class TocService {
|
||||||
tocList = new ReplaySubject<TocItem[]>(1);
|
tocList = new ReplaySubject<TocItem[]>(1);
|
||||||
activeItemIndex = new ReplaySubject<number | null>(1);
|
activeItemIndex = new ReplaySubject<number | null>(1);
|
||||||
private scrollSpyInfo: ScrollSpyInfo | null;
|
private scrollSpyInfo: ScrollSpyInfo | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DOCUMENT) private document: any,
|
@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
|
// This bad boy exists only to strip off the anchor link attached to a heading
|
||||||
private extractHeadingSafeHtml(heading: HTMLHeadingElement) {
|
private extractHeadingSafeHtml(heading: HTMLHeadingElement) {
|
||||||
const a = this.document.createElement('a') as HTMLAnchorElement;
|
const div: HTMLDivElement = this.document.createElement('div');
|
||||||
a.innerHTML = heading.innerHTML;
|
div.innerHTML = heading.innerHTML;
|
||||||
const anchorLink = a.querySelector('a');
|
const anchorLinks: NodeListOf<HTMLAnchorElement> = div.querySelectorAll('a');
|
||||||
if (anchorLink) {
|
for (let i = 0; i < anchorLinks.length; i++) {
|
||||||
a.removeChild(anchorLink);
|
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
|
// security: the document element which provides this heading content
|
||||||
// is always authored by the documentation team and is considered to be safe
|
// 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[] {
|
private findTocHeadings(docElement: Element): HTMLHeadingElement[] {
|
||||||
|
|
Loading…
Reference in New Issue