fix(aio): fix window title on Home page (#20440)
Using `display: none` on the `<h1>` causes `innerText` to not work as expected and include the icon ligature (`link`) in the title. This caused the window title on the angular.io Home page to appear as "Angular - link". This commit fixes it by not generating anchors at all for headings with the `no-anchor` class. Fixes #20427 PR Close #20440
This commit is contained in:
		
							parent
							
								
									c28b52187a
								
							
						
					
					
						commit
						7e38f4fd1f
					
				| @ -25,7 +25,7 @@ | ||||
| <!-- MAIN CONTENT --> | ||||
| <article> | ||||
| 
 | ||||
|   <h1 class="no-toc" style="display: none"></h1> | ||||
|   <h1 class="no-anchor no-toc" style="display: none"></h1> | ||||
| 
 | ||||
|   <div class="home-rows"> | ||||
| 
 | ||||
|  | ||||
| @ -15,6 +15,16 @@ describe('site App', function() { | ||||
|     expect(page.getDocViewerText()).toMatch(/Progressive web apps/i); | ||||
|   }); | ||||
| 
 | ||||
|   it('should set appropriate window titles', () => { | ||||
|     expect(browser.getTitle()).toBe('Angular'); | ||||
| 
 | ||||
|     page.getTopMenuLink('features').click(); | ||||
|     expect(browser.getTitle()).toBe('Angular - FEATURES & BENEFITS'); | ||||
| 
 | ||||
|     page.homeLink.click(); | ||||
|     expect(browser.getTitle()).toBe('Angular'); | ||||
|   }); | ||||
| 
 | ||||
|   it('should show the tutorial index page at `/tutorial/` after jitterbugging through features', () => { | ||||
|     // check that we can navigate directly to the tutorial page
 | ||||
|     page.navigateTo('tutorial/'); | ||||
|  | ||||
| @ -5,6 +5,7 @@ const githubRegex = /https:\/\/github.com\/angular\/angular\//; | ||||
| export class SitePage { | ||||
| 
 | ||||
|   links = element.all(by.css('md-toolbar a')); | ||||
|   homeLink = element(by.css('a.home')); | ||||
|   docsMenuLink = element(by.cssContainingText('aio-top-menu a', 'Docs')); | ||||
|   docViewer = element(by.css('aio-doc-viewer')); | ||||
|   codeExample = element.all(by.css('aio-doc-viewer pre > code')); | ||||
|  | ||||
| @ -126,7 +126,6 @@ | ||||
|     "lunr": "^2.1.0", | ||||
|     "protractor": "^5.2.0", | ||||
|     "rehype": "^4.0.0", | ||||
|     "rehype-autolink-headings": "^2.0.0", | ||||
|     "rehype-slug": "^2.0.0", | ||||
|     "remark": "^7.0.0", | ||||
|     "remark-html": "^6.0.0", | ||||
|  | ||||
| @ -361,6 +361,22 @@ describe('DocViewerComponent', () => { | ||||
|       fixture.detectChanges(); | ||||
|       expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Text Content'); | ||||
|     }); | ||||
| 
 | ||||
|     it('should still use `innerText` if available but empty', () => { | ||||
|       const querySelector_ = docViewerEl.querySelector; | ||||
|       spyOn(docViewerEl, 'querySelector').and.callFake((selector: string) => { | ||||
|         const elem = querySelector_.call(docViewerEl, selector); | ||||
|         Object.defineProperties(elem, { | ||||
|           innerText: { value: '' }, | ||||
|           textContent: { value: 'Text Content' } | ||||
|         }); | ||||
|         return elem; | ||||
|       }); | ||||
| 
 | ||||
|       setCurrentDoc('<h1><i style="visibility: hidden">link</i></h1>Some content'); | ||||
|       fixture.detectChanges(); | ||||
|       expect(titleService.setTitle).toHaveBeenCalledWith('Angular'); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('TOC', () => { | ||||
|  | ||||
| @ -92,17 +92,19 @@ export class DocViewerComponent implements DoCheck, OnDestroy { | ||||
| 
 | ||||
|   private addTitleAndToc(docId: string) { | ||||
|     this.tocService.reset(); | ||||
|     let title = ''; | ||||
|     const titleEl = this.hostElement.querySelector('h1'); | ||||
|     let title = ''; | ||||
| 
 | ||||
|     // Only create TOC for docs with an <h1> title
 | ||||
|     // If you don't want a TOC, add "no-toc" class to <h1>
 | ||||
|     if (titleEl) { | ||||
|       title = (titleEl.innerText || titleEl.textContent).trim(); | ||||
|       title = (typeof titleEl.innerText === 'string') ? titleEl.innerText : titleEl.textContent; | ||||
|       if (!/(no-toc|notoc)/i.test(titleEl.className)) { | ||||
|         this.tocService.genToc(this.hostElement, docId); | ||||
|         titleEl.insertAdjacentHTML('afterend', '<aio-toc class="embedded"></aio-toc>'); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     this.titleService.setTitle(title ? `Angular - ${title}` : 'Angular'); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -1,9 +1,34 @@ | ||||
| const has = require('hast-util-has-property'); | ||||
| const is = require('hast-util-is-element'); | ||||
| const slug = require('rehype-slug'); | ||||
| const link = require('rehype-autolink-headings'); | ||||
| const visit = require('unist-util-visit'); | ||||
| 
 | ||||
| /** | ||||
|  * Get remark to inject anchors into headings | ||||
|  * Get remark to add IDs to headings and inject anchors into them. | ||||
|  * This is a stripped-down equivalent of [rehype-autolink-headings](https://github.com/wooorm/rehype-autolink-headings)
 | ||||
|  * that supports ignoring headings with the `no-anchor` class. | ||||
|  */ | ||||
| const HEADINGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; | ||||
| const NO_ANCHOR_CLASS = 'no-anchor'; | ||||
| 
 | ||||
| const clone = obj => JSON.parse(JSON.stringify(obj)); | ||||
| const hasClass = (node, cls) => { | ||||
|   const className = node.properties.className; | ||||
|   return className && className.includes(cls); | ||||
| }; | ||||
| 
 | ||||
| const link = options => | ||||
|   tree => visit(tree, node => { | ||||
|     if (is(node, HEADINGS) && has(node, 'id') && !hasClass(node, NO_ANCHOR_CLASS)) { | ||||
|       node.children.unshift({ | ||||
|         type: 'element', | ||||
|         tagName: 'a', | ||||
|         properties: Object.assign(clone(options.properties), {href: `#${node.properties.id}`}), | ||||
|         children: clone(options.content) | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
| module.exports = [ | ||||
|   slug, | ||||
|   [link, { | ||||
| @ -12,11 +37,13 @@ module.exports = [ | ||||
|       className: ['header-link'], | ||||
|       'aria-hidden': 'true' | ||||
|     }, | ||||
|     content: { | ||||
|       type: 'element', | ||||
|       tagName: 'i', | ||||
|       properties: {className: ['material-icons']}, | ||||
|       children: [{ type: 'text', value: 'link' }] | ||||
|     } | ||||
|     content: [ | ||||
|       { | ||||
|         type: 'element', | ||||
|         tagName: 'i', | ||||
|         properties: {className: ['material-icons']}, | ||||
|         children: [{ type: 'text', value: 'link' }] | ||||
|       } | ||||
|     ] | ||||
|   }] | ||||
| ]; | ||||
|  | ||||
| @ -14,20 +14,37 @@ describe('autolink-headings postprocessor', () => { | ||||
|   }); | ||||
| 
 | ||||
|   it('should add anchors to headings', () => { | ||||
|     const docs = [ { | ||||
|       docType: 'a', | ||||
|       renderedContent: ` | ||||
|         <h1>Heading 1</h2> | ||||
|         <h2>Heading with <strong>bold</strong></h2> | ||||
|         <h3>Heading with encoded chars &</h3> | ||||
|     ` | ||||
|     }]; | ||||
|     const originalContent = ` | ||||
|       <h1>Heading 1</h2> | ||||
|       <h2>Heading with <strong>bold</strong></h2> | ||||
|       <h3>Heading with encoded chars &</h3> | ||||
|     `;
 | ||||
|     const processedContent = ` | ||||
|       <h1 id="heading-1"><a title="Link to this heading" class="header-link" aria-hidden="true" href="#heading-1"><i class="material-icons">link</i></a>Heading 1</h1> | ||||
|       <h2 id="heading-with-bold"><a title="Link to this heading" class="header-link" aria-hidden="true" href="#heading-with-bold"><i class="material-icons">link</i></a>Heading with <strong>bold</strong></h2> | ||||
|       <h3 id="heading-with-encoded-chars-"><a title="Link to this heading" class="header-link" aria-hidden="true" href="#heading-with-encoded-chars-"><i class="material-icons">link</i></a>Heading with encoded chars &</h3> | ||||
|     `;
 | ||||
| 
 | ||||
|     const docs = [{docType: 'a', renderedContent: originalContent}]; | ||||
|     processor.$process(docs); | ||||
|     expect(docs[0].renderedContent).toEqual(` | ||||
|         <h1 id="heading-1"><a title="Link to this heading" class="header-link" aria-hidden="true" href="#heading-1"><i class="material-icons">link</i></a>Heading 1</h1> | ||||
|         <h2 id="heading-with-bold"><a title="Link to this heading" class="header-link" aria-hidden="true" href="#heading-with-bold"><i class="material-icons">link</i></a>Heading with <strong>bold</strong></h2> | ||||
|         <h3 id="heading-with-encoded-chars-"><a title="Link to this heading" class="header-link" aria-hidden="true" href="#heading-with-encoded-chars-"><i class="material-icons">link</i></a>Heading with encoded chars &</h3> | ||||
|     `);
 | ||||
|     expect(docs[0].renderedContent).toBe(processedContent); | ||||
|   }); | ||||
| 
 | ||||
|   it('should ignore headings with the `no-anchor` class', () => { | ||||
|     const originalContent = ` | ||||
|       <h1 class="no-anchor">Heading 1</h2> | ||||
|       <h2 class="no-anchor">Heading with <strong>bold</strong></h2> | ||||
|       <h3 class="no-anchor">Heading with encoded chars &</h3> | ||||
|     `;
 | ||||
|     const processedContent = ` | ||||
|       <h1 class="no-anchor" id="heading-1">Heading 1</h1> | ||||
|       <h2 class="no-anchor" id="heading-with-bold">Heading with <strong>bold</strong></h2> | ||||
|       <h3 class="no-anchor" id="heading-with-encoded-chars-">Heading with encoded chars &</h3> | ||||
|     `;
 | ||||
| 
 | ||||
|     const docs = [{docType: 'a', renderedContent: originalContent}]; | ||||
|     processor.$process(docs); | ||||
|     expect(docs[0].renderedContent).toBe(processedContent); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
|  | ||||
| @ -2977,7 +2977,7 @@ express@^4.13.3: | ||||
|     utils-merge "1.0.1" | ||||
|     vary "~1.1.2" | ||||
| 
 | ||||
| extend@3, extend@^3.0.0, extend@^3.0.1, extend@~3.0.0, extend@~3.0.1: | ||||
| extend@3, extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: | ||||
|   version "3.0.1" | ||||
|   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" | ||||
| 
 | ||||
| @ -6999,15 +6999,6 @@ regjsparser@^0.1.4: | ||||
|   dependencies: | ||||
|     jsesc "~0.5.0" | ||||
| 
 | ||||
| rehype-autolink-headings@^2.0.0: | ||||
|   version "2.0.2" | ||||
|   resolved "https://registry.yarnpkg.com/rehype-autolink-headings/-/rehype-autolink-headings-2.0.2.tgz#48c7161b1a1020e942c758eb6b2c55cb1bc504d0" | ||||
|   dependencies: | ||||
|     extend "^3.0.1" | ||||
|     hast-util-has-property "^1.0.0" | ||||
|     hast-util-is-element "^1.0.0" | ||||
|     unist-util-visit "^1.1.0" | ||||
| 
 | ||||
| rehype-parse@^3.0.0: | ||||
|   version "3.1.0" | ||||
|   resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-3.1.0.tgz#7f5227a597a3f39fc4b938646161539c444ee728" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user