aio: add h1 title to floating table of contents (#16959)
* refactor(aio): use explicit CSS class for TOC container This makes the styling less fragile to changes in the HTML * fix(aio): schedule TocComponent.activeIndex updates via AsapScheduler We use the `asap` scheduler because updates to `activeItemIndex` are triggered by DOM changes, which, in turn, are caused by the rendering that happened due to a ChangeDetection. Without asap, we would be updating the model while still in a ChangeDetection handler, which is disallowed by Angular. * refactor(aio): do not instantiate floating ToC if not displayed * feat(aio): display the h1 at the top of the floating TOC Closes #16900 * refactor(aio): combine the TOC booleans flags into a "type" state * refactor(aio): remove unnecessary `hostElement` property * fix(aio): ensure that transition works on TOC * fix(aio): use strict equality in ToC template
This commit is contained in:
		
							parent
							
								
									b0c5018c70
								
							
						
					
					
						commit
						966eb2fbd0
					
				| @ -35,9 +35,7 @@ | |||||||
| 
 | 
 | ||||||
| </md-sidenav-container> | </md-sidenav-container> | ||||||
| 
 | 
 | ||||||
| 
 | <div *ngIf="showFloatingToc" class="toc-container" [style.max-height.px]="tocMaxHeight"> | ||||||
| 
 |  | ||||||
| <div class="toc-container" [style.max-height.px]="tocMaxHeight"> |  | ||||||
|   <aio-toc></aio-toc> |   <aio-toc></aio-toc> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -75,6 +75,13 @@ describe('AppComponent', () => { | |||||||
|         component.onResize(500); |         component.onResize(500); | ||||||
|         expect(component.isSideBySide).toBe(false); |         expect(component.isSideBySide).toBe(false); | ||||||
|       }); |       }); | ||||||
|  | 
 | ||||||
|  |       it('should update `showFloatingToc` accordingly', () => { | ||||||
|  |         component.onResize(801); | ||||||
|  |         expect(component.showFloatingToc).toBe(true); | ||||||
|  |         component.onResize(800); | ||||||
|  |         expect(component.showFloatingToc).toBe(false); | ||||||
|  |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe('onScroll', () => { |     describe('onScroll', () => { | ||||||
|  | |||||||
| @ -65,8 +65,12 @@ export class AppComponent implements OnInit { | |||||||
|   sideNavNodes: NavigationNode[]; |   sideNavNodes: NavigationNode[]; | ||||||
|   topMenuNodes: NavigationNode[]; |   topMenuNodes: NavigationNode[]; | ||||||
|   topMenuNarrowNodes: NavigationNode[]; |   topMenuNarrowNodes: NavigationNode[]; | ||||||
|  | 
 | ||||||
|  |   showFloatingToc = false; | ||||||
|  |   showFloatingTocWidth = 800; | ||||||
|   tocMaxHeight: string; |   tocMaxHeight: string; | ||||||
|   private tocMaxHeightOffset = 0; |   private tocMaxHeightOffset = 0; | ||||||
|  | 
 | ||||||
|   versionInfo: VersionInfo; |   versionInfo: VersionInfo; | ||||||
| 
 | 
 | ||||||
|   get homeImageUrl() { |   get homeImageUrl() { | ||||||
| @ -198,6 +202,7 @@ export class AppComponent implements OnInit { | |||||||
|   @HostListener('window:resize', ['$event.target.innerWidth']) |   @HostListener('window:resize', ['$event.target.innerWidth']) | ||||||
|   onResize(width) { |   onResize(width) { | ||||||
|     this.isSideBySide = width > this.sideBySideWidth; |     this.isSideBySide = width > this.sideBySideWidth; | ||||||
|  |     this.showFloatingToc = width > this.showFloatingTocWidth; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   @HostListener('click', ['$event.target', '$event.button', '$event.ctrlKey', '$event.metaKey', '$event.altKey']) |   @HostListener('click', ['$event.target', '$event.button', '$event.ctrlKey', '$event.metaKey', '$event.altKey']) | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| <div *ngIf="hasToc" [class.collapsed]="isCollapsed"> | <div *ngIf="type !== 'None'" class="toc-inner" [class.collapsed]="isCollapsed"> | ||||||
|   <button *ngIf="!isEmbedded" type="button" class="toc-heading" |  | ||||||
|     (click)="toTop()" title="Top of page">Contents</button> |  | ||||||
| 
 | 
 | ||||||
|   <div *ngIf="!hasSecondary && isEmbedded" class="toc-heading embedded">Contents</div> |   <div *ngIf="type === 'EmbeddedSimple'" class="toc-heading embedded"> | ||||||
|  |     Contents | ||||||
|  |   </div> | ||||||
| 
 | 
 | ||||||
|   <button *ngIf="hasSecondary" type="button" class="toc-heading embedded secondary" |   <button *ngIf="type === 'EmbeddedExpandable'" type="button" (click)="toggle(false)" | ||||||
|     (click)="toggle(false)" |     class="toc-heading embedded secondary" | ||||||
|     title="Expand/collapse contents" |     title="Expand/collapse contents" | ||||||
|     aria-label="Expand/collapse contents" |     aria-label="Expand/collapse contents" | ||||||
|     [attr.aria-pressed]="!isCollapsed"> |     [attr.aria-pressed]="!isCollapsed"> | ||||||
| @ -14,13 +14,15 @@ | |||||||
|   </button> |   </button> | ||||||
| 
 | 
 | ||||||
|   <ul class="toc-list"> |   <ul class="toc-list"> | ||||||
|     <li #tocItem *ngFor="let toc of tocList; let i = index" title="{{toc.title}}" |     <ng-container *ngFor="let toc of tocList; let i = index"> | ||||||
|       class="{{toc.level}}" [class.secondary]="toc.isSecondary" [class.active]="i === activeIndex"> |       <li #tocItem title="{{toc.title}}" *ngIf="type === 'Floating' || toc.level !== 'h1'" | ||||||
|  |         class="{{toc.level}}" [class.secondary]="type === 'EmbeddedExpandable' && i >= primaryMax" [class.active]="i === activeIndex"> | ||||||
|         <a [href]="toc.href" [innerHTML]="toc.content"></a> |         <a [href]="toc.href" [innerHTML]="toc.content"></a> | ||||||
|       </li> |       </li> | ||||||
|  |     </ng-container> | ||||||
|   </ul> |   </ul> | ||||||
| 
 | 
 | ||||||
|   <button type="button" (click)="toggle()" *ngIf="hasSecondary" |   <button *ngIf="type === 'EmbeddedExpandable'" type="button" (click)="toggle()" | ||||||
|     class="toc-more-items embedded material-icons" [class.collapsed]="isCollapsed" |     class="toc-more-items embedded material-icons" [class.collapsed]="isCollapsed" | ||||||
|     title="Expand/collapse contents" |     title="Expand/collapse contents" | ||||||
|     aria-label="Expand/collapse contents" |     aria-label="Expand/collapse contents" | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| import { Component, CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core'; | import { Component, CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core'; | ||||||
| import { async, ComponentFixture, TestBed } from '@angular/core/testing'; | import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||||
| import { By, DOCUMENT } from '@angular/platform-browser'; | import { By, DOCUMENT } from '@angular/platform-browser'; | ||||||
| import { BehaviorSubject } from 'rxjs/BehaviorSubject'; | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; | ||||||
|  | import { asap } from 'rxjs/scheduler/asap'; | ||||||
| 
 | 
 | ||||||
| import { ScrollService } from 'app/shared/scroll.service'; | import { ScrollService } from 'app/shared/scroll.service'; | ||||||
| import { TocComponent } from './toc.component'; | import { TocComponent } from './toc.component'; | ||||||
| @ -16,7 +17,7 @@ describe('TocComponent', () => { | |||||||
|     listItems: DebugElement[]; |     listItems: DebugElement[]; | ||||||
|     tocHeading: DebugElement; |     tocHeading: DebugElement; | ||||||
|     tocHeadingButtonEmbedded: DebugElement; |     tocHeadingButtonEmbedded: DebugElement; | ||||||
|     tocHeadingButtonSide: DebugElement; |     tocH1Heading: DebugElement; | ||||||
|     tocMoreButton: DebugElement; |     tocMoreButton: DebugElement; | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
| @ -25,7 +26,7 @@ describe('TocComponent', () => { | |||||||
|       listItems: tocComponentDe.queryAll(By.css('ul.toc-list>li')), |       listItems: tocComponentDe.queryAll(By.css('ul.toc-list>li')), | ||||||
|       tocHeading: tocComponentDe.query(By.css('.toc-heading')), |       tocHeading: tocComponentDe.query(By.css('.toc-heading')), | ||||||
|       tocHeadingButtonEmbedded: tocComponentDe.query(By.css('button.toc-heading.embedded')), |       tocHeadingButtonEmbedded: tocComponentDe.query(By.css('button.toc-heading.embedded')), | ||||||
|       tocHeadingButtonSide: tocComponentDe.query(By.css('button.toc-heading:not(.embedded)')), |       tocH1Heading: tocComponentDe.query(By.css('.h1')), | ||||||
|       tocMoreButton: tocComponentDe.query(By.css('button.toc-more-items')), |       tocMoreButton: tocComponentDe.query(By.css('button.toc-more-items')), | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| @ -60,40 +61,51 @@ describe('TocComponent', () => { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should not display a ToC initially', () => { |     it('should not display a ToC initially', () => { | ||||||
|       expect(tocComponent.hasToc).toBe(false); |       expect(tocComponent.type).toEqual('None'); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should not display anything when no TocItems', () => { |     it('should not display anything when no h2 or h3 TocItems', () => { | ||||||
|       tocService.tocList.next([]); |       tocService.tocList.next([tocItem('H1', 'h1')]); | ||||||
|       fixture.detectChanges(); |       fixture.detectChanges(); | ||||||
|       expect(tocComponentDe.children.length).toEqual(0); |       expect(tocComponentDe.children.length).toEqual(0); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should update when the TocItems are updated', () => { |     it('should update when the TocItems are updated', () => { | ||||||
|       tocService.tocList.next([{}] as TocItem[]); |       tocService.tocList.next([tocItem('Heading A')]); | ||||||
|       fixture.detectChanges(); |       fixture.detectChanges(); | ||||||
|       expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(1); |       expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(1); | ||||||
| 
 | 
 | ||||||
|       tocService.tocList.next([{}, {}, {}] as TocItem[]); |       tocService.tocList.next([tocItem('Heading A'), tocItem('Heading B'), tocItem('Heading C')]); | ||||||
|       fixture.detectChanges(); |       fixture.detectChanges(); | ||||||
|       expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(3); |       expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(3); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     it('should only display H2 and H3 TocItems', () => { | ||||||
|  |       tocService.tocList.next([tocItem('Heading A', 'h1'), tocItem('Heading B'), tocItem('Heading C', 'h3')]); | ||||||
|  |       fixture.detectChanges(); | ||||||
|  |       const items = tocComponentDe.queryAllNodes(By.css('li')); | ||||||
|  |       expect(items.length).toBe(2); | ||||||
|  |       expect(items.find(item => item.nativeNode.innerText === 'Heading A')).toBeFalsy(); | ||||||
|  |       expect(items.find(item => item.nativeNode.innerText === 'Heading B')).toBeTruthy(); | ||||||
|  |       expect(items.find(item => item.nativeNode.innerText === 'Heading C')).toBeTruthy(); | ||||||
|  |       expect(setPage().tocH1Heading).toBeFalsy(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     it('should stop listening for TocItems once destroyed', () => { |     it('should stop listening for TocItems once destroyed', () => { | ||||||
|       tocService.tocList.next([{}] as TocItem[]); |       tocService.tocList.next([tocItem('Heading A')]); | ||||||
|       fixture.detectChanges(); |       fixture.detectChanges(); | ||||||
|       expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(1); |       expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(1); | ||||||
| 
 | 
 | ||||||
|       tocComponent.ngOnDestroy(); |       tocComponent.ngOnDestroy(); | ||||||
|       tocService.tocList.next([{}, {}, {}] as TocItem[]); |       tocService.tocList.next([tocItem('Heading A', 'h1'), tocItem('Heading B'), tocItem('Heading C')]); | ||||||
|       fixture.detectChanges(); |       fixture.detectChanges(); | ||||||
|       expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(1); |       expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(1); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe('when four TocItems', () => { |     describe('when fewer than `maxPrimary` TocItems', () => { | ||||||
| 
 | 
 | ||||||
|       beforeEach(() => { |       beforeEach(() => { | ||||||
|         tocService.tocList.next([{}, {}, {}, {}] as TocItem[]); |         tocService.tocList.next([tocItem('Heading A'), tocItem('Heading B'), tocItem('Heading C'), tocItem('Heading D')]); | ||||||
|         fixture.detectChanges(); |         fixture.detectChanges(); | ||||||
|         page = setPage(); |         page = setPage(); | ||||||
|       }); |       }); | ||||||
| @ -103,7 +115,7 @@ describe('TocComponent', () => { | |||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       it('should not have secondary items', () => { |       it('should not have secondary items', () => { | ||||||
|         expect(tocComponent.hasSecondary).toEqual(false, 'hasSecondary flag'); |         expect(tocComponent.type).toEqual('EmbeddedSimple'); | ||||||
|         const aSecond = page.listItems.find(item => item.classes.secondary); |         const aSecond = page.listItems.find(item => item.classes.secondary); | ||||||
|         expect(aSecond).toBeFalsy('should not find a secondary'); |         expect(aSecond).toBeFalsy('should not find a secondary'); | ||||||
|       }); |       }); | ||||||
| @ -128,7 +140,10 @@ describe('TocComponent', () => { | |||||||
|         tocService.tocList.subscribe(v => tocList = v); |         tocService.tocList.subscribe(v => tocList = v); | ||||||
| 
 | 
 | ||||||
|         expect(page.listItems.length).toBeGreaterThan(4); |         expect(page.listItems.length).toBeGreaterThan(4); | ||||||
|         expect(page.listItems.length).toEqual(tocList.length); |       }); | ||||||
|  | 
 | ||||||
|  |       it('should not display the h1 item', () => { | ||||||
|  |         expect(page.listItems.find(item => item.classes.h1)).toBeFalsy('should not find h1 item'); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       it('should be in "collapsed" (not expanded) state at the start', () => { |       it('should be in "collapsed" (not expanded) state at the start', () => { | ||||||
| @ -145,14 +160,13 @@ describe('TocComponent', () => { | |||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       it('should have secondary items', () => { |       it('should have secondary items', () => { | ||||||
|         expect(tocComponent.hasSecondary).toEqual(true, 'hasSecondary flag'); |         expect(tocComponent.type).toEqual('EmbeddedExpandable'); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       // CSS should hide items with the secondary class when collapsed
 |       // CSS will hide items with the secondary class when collapsed
 | ||||||
|       it('should have secondary item with a secondary class', () => { |       it('should have secondary item with a secondary class', () => { | ||||||
|         const aSecondary = page.listItems.find(item => item.classes.secondary); |         const aSecondary = page.listItems.find(item => item.classes.secondary); | ||||||
|         expect(aSecondary).toBeTruthy('should find a secondary'); |         expect(aSecondary).toBeTruthy('should find a secondary'); | ||||||
|         expect(aSecondary.classes.secondary).toEqual(true, 'has secondary class'); |  | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       describe('after click tocHeading button', () => { |       describe('after click tocHeading button', () => { | ||||||
| @ -245,17 +259,15 @@ describe('TocComponent', () => { | |||||||
| 
 | 
 | ||||||
|     it('should not be in embedded state', () => { |     it('should not be in embedded state', () => { | ||||||
|       expect(tocComponent.isEmbedded).toEqual(false); |       expect(tocComponent.isEmbedded).toEqual(false); | ||||||
|  |       expect(tocComponent.type).toEqual('Floating'); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should display all items', () => { |     it('should display all items (including h1s)', () => { | ||||||
|       let tocList: TocItem[]; |       expect(page.listItems.length).toEqual(getTestTocList().length); | ||||||
|       tocService.tocList.subscribe(v => tocList = v); |  | ||||||
| 
 |  | ||||||
|       expect(page.listItems.length).toEqual(tocList.length); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should not have secondary items', () => { |     it('should not have secondary items', () => { | ||||||
|       expect(tocComponent.hasSecondary).toEqual(false, 'hasSecondary flag'); |       expect(tocComponent.type).toEqual('Floating'); | ||||||
|       const aSecond = page.listItems.find(item => item.classes.secondary); |       const aSecond = page.listItems.find(item => item.classes.secondary); | ||||||
|       expect(aSecond).toBeFalsy('should not find a secondary'); |       expect(aSecond).toBeFalsy('should not find a secondary'); | ||||||
|     }); |     }); | ||||||
| @ -265,37 +277,31 @@ describe('TocComponent', () => { | |||||||
|       expect(page.tocMoreButton).toBeFalsy('bottom more button'); |       expect(page.tocMoreButton).toBeFalsy('bottom more button'); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should display "Contents" button', () => { |     it('should display H1 title', () => { | ||||||
|       expect(page.tocHeadingButtonSide).toBeTruthy(); |       expect(page.tocH1Heading).toBeTruthy(); | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     it('should scroll to top when "Contents" button clicked', () => { |  | ||||||
|       page.tocHeadingButtonSide.nativeElement.click(); |  | ||||||
|       fixture.detectChanges(); |  | ||||||
|       expect(scrollToTopSpy).toHaveBeenCalled(); |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe('#activeIndex', () => { |     describe('#activeIndex', () => { | ||||||
|       it('should keep track of `TocService`\'s `activeItemIndex`', () => { |       it('should keep track of `TocService`\'s `activeItemIndex`', () => { | ||||||
|         expect(tocComponent.activeIndex).toBeNull(); |         expect(tocComponent.activeIndex).toBeNull(); | ||||||
| 
 | 
 | ||||||
|         tocService.activeItemIndex.next(42); |         tocService.setActiveIndex(42); | ||||||
|         expect(tocComponent.activeIndex).toBe(42); |         expect(tocComponent.activeIndex).toBe(42); | ||||||
| 
 | 
 | ||||||
|         tocService.activeItemIndex.next(null); |         tocService.setActiveIndex(null); | ||||||
|         expect(tocComponent.activeIndex).toBeNull(); |         expect(tocComponent.activeIndex).toBeNull(); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       it('should stop tracking `activeItemIndex` once destroyed', () => { |       it('should stop tracking `activeItemIndex` once destroyed', () => { | ||||||
|         tocService.activeItemIndex.next(42); |         tocService.setActiveIndex(42); | ||||||
|         expect(tocComponent.activeIndex).toBe(42); |         expect(tocComponent.activeIndex).toBe(42); | ||||||
| 
 | 
 | ||||||
|         tocComponent.ngOnDestroy(); |         tocComponent.ngOnDestroy(); | ||||||
| 
 | 
 | ||||||
|         tocService.activeItemIndex.next(43); |         tocService.setActiveIndex(43); | ||||||
|         expect(tocComponent.activeIndex).toBe(42); |         expect(tocComponent.activeIndex).toBe(42); | ||||||
| 
 | 
 | ||||||
|         tocService.activeItemIndex.next(null); |         tocService.setActiveIndex(null); | ||||||
|         expect(tocComponent.activeIndex).toBe(42); |         expect(tocComponent.activeIndex).toBe(42); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
| @ -334,19 +340,19 @@ describe('TocComponent', () => { | |||||||
| 
 | 
 | ||||||
|         tocComponent.activeIndex = 1; |         tocComponent.activeIndex = 1; | ||||||
|         fixture.detectChanges(); |         fixture.detectChanges(); | ||||||
|         expect(getActiveTextContent()).toBe('H2 Two'); |         expect(getActiveTextContent()).toBe('Heading one'); | ||||||
| 
 | 
 | ||||||
|         tocComponent.tocList = [{content: 'New 1'}, {content: 'New 2'}] as any as TocItem[]; |         tocComponent.tocList = [tocItem('New 1'), tocItem('New 2')]; | ||||||
|         fixture.detectChanges(); |         fixture.detectChanges(); | ||||||
|         page = setPage(); |         page = setPage(); | ||||||
|         expect(getActiveTextContent()).toBe('New 2'); |         expect(getActiveTextContent()).toBe('New 2'); | ||||||
| 
 | 
 | ||||||
|         tocComponent.tocList.unshift({content: 'New 0'} as any as TocItem); |         tocComponent.tocList.unshift(tocItem('New 0')); | ||||||
|         fixture.detectChanges(); |         fixture.detectChanges(); | ||||||
|         page = setPage(); |         page = setPage(); | ||||||
|         expect(getActiveTextContent()).toBe('New 1'); |         expect(getActiveTextContent()).toBe('New 1'); | ||||||
| 
 | 
 | ||||||
|         tocComponent.tocList = [{content: 'Very New 1'}] as any as TocItem[]; |         tocComponent.tocList = [tocItem('Very New 1')]; | ||||||
|         fixture.detectChanges(); |         fixture.detectChanges(); | ||||||
|         page = setPage(); |         page = setPage(); | ||||||
|         expect(page.listItems.findIndex(By.css('.active'))).toBe(-1); |         expect(page.listItems.findIndex(By.css('.active'))).toBe(-1); | ||||||
| @ -373,17 +379,17 @@ describe('TocComponent', () => { | |||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         it('when the `activeIndex` changes', () => { |         it('when the `activeIndex` changes', () => { | ||||||
|           tocService.activeItemIndex.next(0); |           tocService.setActiveIndex(0); | ||||||
|           fixture.detectChanges(); |           fixture.detectChanges(); | ||||||
| 
 | 
 | ||||||
|           expect(parentScrollTop).toBe(0); |           expect(parentScrollTop).toBe(0); | ||||||
| 
 | 
 | ||||||
|           tocService.activeItemIndex.next(1); |           tocService.setActiveIndex(1); | ||||||
|           fixture.detectChanges(); |           fixture.detectChanges(); | ||||||
| 
 | 
 | ||||||
|           expect(parentScrollTop).toBe(0); |           expect(parentScrollTop).toBe(0); | ||||||
| 
 | 
 | ||||||
|           tocService.activeItemIndex.next(page.listItems.length - 1); |           tocService.setActiveIndex(page.listItems.length - 1); | ||||||
|           fixture.detectChanges(); |           fixture.detectChanges(); | ||||||
| 
 | 
 | ||||||
|           expect(parentScrollTop).toBeGreaterThan(0); |           expect(parentScrollTop).toBeGreaterThan(0); | ||||||
| @ -397,7 +403,7 @@ describe('TocComponent', () => { | |||||||
| 
 | 
 | ||||||
|           expect(parentScrollTop).toBe(0); |           expect(parentScrollTop).toBe(0); | ||||||
| 
 | 
 | ||||||
|           tocService.activeItemIndex.next(tocList.length - 1); |           tocService.setActiveIndex(tocList.length - 1); | ||||||
|           fixture.detectChanges(); |           fixture.detectChanges(); | ||||||
| 
 | 
 | ||||||
|           expect(parentScrollTop).toBe(0); |           expect(parentScrollTop).toBe(0); | ||||||
| @ -412,7 +418,7 @@ describe('TocComponent', () => { | |||||||
|           const tocList = tocComponent.tocList; |           const tocList = tocComponent.tocList; | ||||||
|           tocComponent.ngOnDestroy(); |           tocComponent.ngOnDestroy(); | ||||||
| 
 | 
 | ||||||
|           tocService.activeItemIndex.next(page.listItems.length - 1); |           tocService.setActiveIndex(page.listItems.length - 1); | ||||||
|           fixture.detectChanges(); |           fixture.detectChanges(); | ||||||
| 
 | 
 | ||||||
|           expect(parentScrollTop).toBe(0); |           expect(parentScrollTop).toBe(0); | ||||||
| @ -453,46 +459,26 @@ class TestScrollService { | |||||||
| class TestTocService { | class TestTocService { | ||||||
|   tocList = new BehaviorSubject<TocItem[]>(getTestTocList()); |   tocList = new BehaviorSubject<TocItem[]>(getTestTocList()); | ||||||
|   activeItemIndex = new BehaviorSubject<number | null>(null); |   activeItemIndex = new BehaviorSubject<number | null>(null); | ||||||
|  |   setActiveIndex(index) { | ||||||
|  |     this.activeItemIndex.next(index); | ||||||
|  |     if (asap.scheduled) { | ||||||
|  |       asap.flush(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function tocItem(title: string, level = 'h2', href = '', content = title) { | ||||||
|  |   return { title, href, level, content }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // tslint:disable:quotemark
 |  | ||||||
| function getTestTocList() { | function getTestTocList() { | ||||||
|   return [ |   return [ | ||||||
|     { |     tocItem('Title',       'h1', 'fizz/buzz#title',                  'Title'), | ||||||
|       "content": "Heading one", |     tocItem('Heading one', 'h2', 'fizz/buzz#heading-one-special-id', 'Heading one'), | ||||||
|       "href": "fizz/buzz#heading-one-special-id", |     tocItem('H2 Two',      'h2', 'fizz/buzz#h2-two',                 'H2 Two'), | ||||||
|       "level": "h2", |     tocItem('H2 Three',    'h2', 'fizz/buzz#h2-three',               'H2 <b>Three</b>'), | ||||||
|       "title": "Heading one" |     tocItem('H3 3a',       'h3', 'fizz/buzz#h3-3a',                  'H3 3a'), | ||||||
|     }, |     tocItem('H3 3b',       'h3', 'fizz/buzz#h3-3b',                  'H3 3b'), | ||||||
|     { |     tocItem('H2 4',        'h2', 'fizz/buzz#h2-four',                '<i>H2 <b>four</b></i>'), | ||||||
|       "content": "H2 Two", |  | ||||||
|       "href": "fizz/buzz#h2-two", |  | ||||||
|       "level": "h2", |  | ||||||
|       "title": "H2 Two" |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       "content": "H2 <b>Three</b>", |  | ||||||
|       "href": "fizz/buzz#h2-three", |  | ||||||
|       "level": "h2", |  | ||||||
|       "title": "H2 Three" |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       "content": "H3 3a", |  | ||||||
|       "href": "fizz/buzz#h3-3a", |  | ||||||
|       "level": "h3", |  | ||||||
|       "title": "H3 3a" |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       "content": "H3 3b", |  | ||||||
|       "href": "fizz/buzz#h3-3b", |  | ||||||
|       "level": "h3", |  | ||||||
|       "title": "H3 3b" |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       "content": "<i>H2 <b>four</b></i>", |  | ||||||
|       "href": "fizz/buzz#h2-four", |  | ||||||
|       "level": "h2", |  | ||||||
|       "title": "H2 4" |  | ||||||
|     } |  | ||||||
|   ]; |   ]; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,12 +1,16 @@ | |||||||
| import { AfterViewInit, Component, ElementRef, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; | import { AfterViewInit, Component, ElementRef, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; | ||||||
| import { Observable } from 'rxjs/Observable'; | import { Observable } from 'rxjs/Observable'; | ||||||
| import { Subject } from 'rxjs/Subject'; | import { Subject } from 'rxjs/Subject'; | ||||||
|  | import { asap } from 'rxjs/scheduler/asap'; | ||||||
| import 'rxjs/add/observable/combineLatest'; | import 'rxjs/add/observable/combineLatest'; | ||||||
|  | import 'rxjs/add/operator/subscribeOn'; | ||||||
| import 'rxjs/add/operator/takeUntil'; | import 'rxjs/add/operator/takeUntil'; | ||||||
| 
 | 
 | ||||||
| import { ScrollService } from 'app/shared/scroll.service'; | import { ScrollService } from 'app/shared/scroll.service'; | ||||||
| import { TocItem, TocService } from 'app/shared/toc.service'; | import { TocItem, TocService } from 'app/shared/toc.service'; | ||||||
| 
 | 
 | ||||||
|  | type TocType = 'None' | 'Floating' | 'EmbeddedSimple' | 'EmbeddedExpandable'; | ||||||
|  | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'aio-toc', |   selector: 'aio-toc', | ||||||
|   templateUrl: 'toc.component.html', |   templateUrl: 'toc.component.html', | ||||||
| @ -15,9 +19,7 @@ import { TocItem, TocService } from 'app/shared/toc.service'; | |||||||
| export class TocComponent implements OnInit, AfterViewInit, OnDestroy { | export class TocComponent implements OnInit, AfterViewInit, OnDestroy { | ||||||
| 
 | 
 | ||||||
|   activeIndex: number | null = null; |   activeIndex: number | null = null; | ||||||
|   hasSecondary = false; |   type: TocType = 'None'; | ||||||
|   hasToc = false; |  | ||||||
|   hostElement: HTMLElement; |  | ||||||
|   isCollapsed = true; |   isCollapsed = true; | ||||||
|   isEmbedded = false; |   isEmbedded = false; | ||||||
|   @ViewChildren('tocItem') private items: QueryList<ElementRef>; |   @ViewChildren('tocItem') private items: QueryList<ElementRef>; | ||||||
| @ -29,37 +31,35 @@ export class TocComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|     private scrollService: ScrollService, |     private scrollService: ScrollService, | ||||||
|     elementRef: ElementRef, |     elementRef: ElementRef, | ||||||
|     private tocService: TocService) { |     private tocService: TocService) { | ||||||
|     this.hostElement = elementRef.nativeElement; |     this.isEmbedded = elementRef.nativeElement.className.indexOf('embedded') !== -1; | ||||||
|     this.isEmbedded = this.hostElement.className.indexOf('embedded') !== -1; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit() { |   ngOnInit() { | ||||||
|     this.tocService.tocList |     this.tocService.tocList | ||||||
|         .takeUntil(this.onDestroy) |         .takeUntil(this.onDestroy) | ||||||
|         .subscribe(tocList => { |         .subscribe(tocList => { | ||||||
|           const count = tocList.length; |  | ||||||
| 
 |  | ||||||
|           this.hasToc = count > 0; |  | ||||||
|           this.hasSecondary = this.isEmbedded && this.hasToc && (count > this.primaryMax); |  | ||||||
|           this.tocList = tocList; |           this.tocList = tocList; | ||||||
|  |           const itemCount = count(this.tocList, item => item.level !== 'h1'); | ||||||
| 
 | 
 | ||||||
|           if (this.hasSecondary) { |           this.type = (itemCount > 0) ? | ||||||
|             for (let i = this.primaryMax; i < count; i++) { |                         this.isEmbedded ? | ||||||
|               tocList[i].isSecondary = true; |                           (itemCount > this.primaryMax) ? | ||||||
|             } |                             'EmbeddedExpandable' : | ||||||
|           } |                           'EmbeddedSimple' : | ||||||
|  |                         'Floating' : | ||||||
|  |                       'None'; | ||||||
|         }); |         }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngAfterViewInit() { |   ngAfterViewInit() { | ||||||
|     if (!this.isEmbedded) { |     if (!this.isEmbedded) { | ||||||
|       this.tocService.activeItemIndex |       // We use the `asap` scheduler because updates to `activeItemIndex` are triggered by DOM changes,
 | ||||||
|           .takeUntil(this.onDestroy) |       // which, in turn, are caused by the rendering that happened due to a ChangeDetection.
 | ||||||
|           .subscribe(index => this.activeIndex = index); |       // Without asap, we would be updating the model while still in a ChangeDetection handler, which is disallowed by Angular.
 | ||||||
| 
 |       Observable.combineLatest(this.tocService.activeItemIndex.subscribeOn(asap), this.items.changes.startWith(this.items)) | ||||||
|       Observable.combineLatest(this.tocService.activeItemIndex, this.items.changes.startWith(this.items)) |  | ||||||
|           .takeUntil(this.onDestroy) |           .takeUntil(this.onDestroy) | ||||||
|           .subscribe(([index, items]) => { |           .subscribe(([index, items]) => { | ||||||
|  |             this.activeIndex = index; | ||||||
|             if (index === null || index >= items.length) { |             if (index === null || index >= items.length) { | ||||||
|               return; |               return; | ||||||
|             } |             } | ||||||
| @ -92,3 +92,7 @@ export class TocComponent implements OnInit, AfterViewInit, OnDestroy { | |||||||
|     this.scrollService.scrollToTop(); |     this.scrollService.scrollToTop(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | function count<T>(array: T[], fn: (T) => boolean) { | ||||||
|  |   return array.reduce((count, item) => fn(item) ? count + 1 : count, 0); | ||||||
|  | } | ||||||
|  | |||||||
| @ -37,28 +37,28 @@ describe('TocService', () => { | |||||||
| 
 | 
 | ||||||
|   describe('tocList', () => { |   describe('tocList', () => { | ||||||
|     it('should emit the latest value to new subscribers', () => { |     it('should emit the latest value to new subscribers', () => { | ||||||
|       const expectedValue1 = {} as TocItem; |       const expectedValue1 = tocItem('Heading A'); | ||||||
|       const expectedValue2 = {} as TocItem; |       const expectedValue2 = tocItem('Heading B'); | ||||||
|       let value1: TocItem[]; |       let value1: TocItem[]; | ||||||
|       let value2: TocItem[]; |       let value2: TocItem[]; | ||||||
| 
 | 
 | ||||||
|       tocService.tocList.next([] as TocItem[]); |       tocService.tocList.next([]); | ||||||
|       tocService.tocList.subscribe(v => value1 = v); |       tocService.tocList.subscribe(v => value1 = v); | ||||||
|       expect(value1).toEqual([]); |       expect(value1).toEqual([]); | ||||||
| 
 | 
 | ||||||
|       tocService.tocList.next([expectedValue1, expectedValue2] as TocItem[]); |       tocService.tocList.next([expectedValue1, expectedValue2]); | ||||||
|       tocService.tocList.subscribe(v => value2 = v); |       tocService.tocList.subscribe(v => value2 = v); | ||||||
|       expect(value2).toEqual([expectedValue1, expectedValue2]); |       expect(value2).toEqual([expectedValue1, expectedValue2]); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should emit the same values to all subscribers', () => { |     it('should emit the same values to all subscribers', () => { | ||||||
|       const expectedValue1 = {} as TocItem; |       const expectedValue1 = tocItem('Heading A'); | ||||||
|       const expectedValue2 = {} as TocItem; |       const expectedValue2 = tocItem('Heading B'); | ||||||
|       const emittedValues: TocItem[][] = []; |       const emittedValues: TocItem[][] = []; | ||||||
| 
 | 
 | ||||||
|       tocService.tocList.subscribe(v => emittedValues.push(v)); |       tocService.tocList.subscribe(v => emittedValues.push(v)); | ||||||
|       tocService.tocList.subscribe(v => emittedValues.push(v)); |       tocService.tocList.subscribe(v => emittedValues.push(v)); | ||||||
|       tocService.tocList.next([expectedValue1, expectedValue2] as TocItem[]); |       tocService.tocList.next([expectedValue1, expectedValue2]); | ||||||
| 
 | 
 | ||||||
|       expect(emittedValues).toEqual([ |       expect(emittedValues).toEqual([ | ||||||
|         [expectedValue1, expectedValue2], |         [expectedValue1, expectedValue2], | ||||||
| @ -153,7 +153,9 @@ describe('TocService', () => { | |||||||
|   describe('should clear tocList', () => { |   describe('should clear tocList', () => { | ||||||
|     beforeEach(() => { |     beforeEach(() => { | ||||||
|       // Start w/ dummy data from previous usage
 |       // Start w/ dummy data from previous usage
 | ||||||
|       tocService.tocList.next([{}, {}] as TocItem[]); |       const expectedValue1 = tocItem('Heading A'); | ||||||
|  |       const expectedValue2 = tocItem('Heading B'); | ||||||
|  |       tocService.tocList.next([expectedValue1, expectedValue2]); | ||||||
|       expect(lastTocList).not.toEqual([]); |       expect(lastTocList).not.toEqual([]); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
| @ -172,8 +174,8 @@ describe('TocService', () => { | |||||||
|       expect(lastTocList).toEqual([]); |       expect(lastTocList).toEqual([]); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('when given doc element w/ headings other than h2 & h3', () => { |     it('when given doc element w/ headings other than h1, h2 & h3', () => { | ||||||
|       callGenToc('<h1>This</h1><h4>and</h4><h5>that</h5>'); |       callGenToc('<h4>and</h4><h5>that</h5>'); | ||||||
|       expect(lastTocList).toEqual([]); |       expect(lastTocList).toEqual([]); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
| @ -232,60 +234,60 @@ describe('TocService', () => { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should have tocList with expect number of TocItems', () => { |     it('should have tocList with expect number of TocItems', () => { | ||||||
|       // should ignore h1, h4, and the no-toc h2
 |       // should ignore h4, and the no-toc h2
 | ||||||
|       expect(lastTocList.length).toEqual(headings.length - 3); |       expect(lastTocList.length).toEqual(headings.length - 2); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should have href with docId and heading\'s id', () => { |     it('should have href with docId and heading\'s id', () => { | ||||||
|       const tocItem = lastTocList[0]; |       const tocItem = lastTocList.find(item => item.title === 'Heading one'); | ||||||
|       expect(tocItem.href).toEqual(`${docId}#heading-one-special-id`); |       expect(tocItem.href).toEqual(`${docId}#heading-one-special-id`); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should have level "h2" for an <h2>', () => { |     it('should have level "h2" for an <h2>', () => { | ||||||
|       const tocItem = lastTocList[0]; |       const tocItem = lastTocList.find(item => item.title === 'Heading one'); | ||||||
|       expect(tocItem.level).toEqual('h2'); |       expect(tocItem.level).toEqual('h2'); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should have level "h3" for an <h3>', () => { |     it('should have level "h3" for an <h3>', () => { | ||||||
|       const tocItem = lastTocList[3]; |       const tocItem = lastTocList.find(item => item.title === 'H3 3a'); | ||||||
|       expect(tocItem.level).toEqual('h3'); |       expect(tocItem.level).toEqual('h3'); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should have title which is heading\'s innerText ', () => { |     it('should have title which is heading\'s innerText ', () => { | ||||||
|       const heading = headings[3]; |       const heading = headings[3]; | ||||||
|       const tocItem = lastTocList[2]; |       const tocItem = lastTocList[3]; | ||||||
|       expect(heading.innerText).toEqual(tocItem.title); |       expect(heading.innerText).toEqual(tocItem.title); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should have "SafeHtml" content which is heading\'s innerHTML ', () => { |     it('should have "SafeHtml" content which is heading\'s innerHTML ', () => { | ||||||
|       const heading = headings[3]; |       const heading = headings[3]; | ||||||
|       const content = lastTocList[2].content; |       const content = lastTocList[3].content; | ||||||
|       expect((<TestSafeHtml>content).changingThisBreaksApplicationSecurity) |       expect((<TestSafeHtml>content).changingThisBreaksApplicationSecurity) | ||||||
|         .toEqual(heading.innerHTML); |         .toEqual(heading.innerHTML); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should calculate and set id of heading without an id', () => { |     it('should calculate and set id of heading without an id', () => { | ||||||
|  |       const tocItem = lastTocList.find(item => item.title === 'H2 Two'); | ||||||
|       const id = headings[2].getAttribute('id'); |       const id = headings[2].getAttribute('id'); | ||||||
|       expect(id).toEqual('h2-two'); |       expect(id).toEqual('h2-two'); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should have href with docId and calculated heading id', () => { |     it('should have href with docId and calculated heading id', () => { | ||||||
|       const tocItem = lastTocList[1]; |       const tocItem = lastTocList.find(item => item.title === 'H2 Two'); | ||||||
|       expect(tocItem.href).toEqual(`${docId}#h2-two`); |       expect(tocItem.href).toEqual(`${docId}#h2-two`); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should ignore HTML in heading when calculating id', () => { |     it('should ignore HTML in heading when calculating id', () => { | ||||||
|       const id = headings[3].getAttribute('id'); |       const id = headings[3].getAttribute('id'); | ||||||
|       const tocItem = lastTocList[2]; |       const tocItem = lastTocList[3]; | ||||||
|       expect(id).toEqual('h2-three', 'heading id'); |       expect(id).toEqual('h2-three', 'heading id'); | ||||||
|       expect(tocItem.href).toEqual(`${docId}#h2-three`, 'tocItem href'); |       expect(tocItem.href).toEqual(`${docId}#h2-three`, 'tocItem href'); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('should avoid repeating an id when calculating', () => { |     it('should avoid repeating an id when calculating', () => { | ||||||
|       const tocItem4a = lastTocList[5]; |       const tocItems = lastTocList.filter(item => item.title === 'H2 4 repeat'); | ||||||
|       const tocItem4b = lastTocList[6]; |       expect(tocItems[0].href).toEqual(`${docId}#h2-4-repeat`, 'first'); | ||||||
|       expect(tocItem4a.href).toEqual(`${docId}#h2-4-repeat`, 'first'); |       expect(tocItems[1].href).toEqual(`${docId}#h2-4-repeat-2`, 'second'); | ||||||
|       expect(tocItem4b.href).toEqual(`${docId}#h2-4-repeat-2`, 'second'); |  | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
| @ -363,3 +365,7 @@ class MockScrollSpyService { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function tocItem(title: string, level = 'h2', href = '', content = title) { | ||||||
|  |   return { title, href, level, content }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| import { Inject, Injectable } from '@angular/core'; | import { Inject, Injectable } from '@angular/core'; | ||||||
| import { DOCUMENT, DomSanitizer, SafeHtml } from '@angular/platform-browser'; | import { DOCUMENT, DomSanitizer, SafeHtml } from '@angular/platform-browser'; | ||||||
| import { ReplaySubject } from 'rxjs/ReplaySubject'; | import { ReplaySubject } from 'rxjs/ReplaySubject'; | ||||||
| 
 |  | ||||||
| import { ScrollSpyInfo, ScrollSpyService } from 'app/shared/scroll-spy.service'; | import { ScrollSpyInfo, ScrollSpyService } from 'app/shared/scroll-spy.service'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -66,7 +65,7 @@ export class TocService { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private findTocHeadings(docElement: Element): HTMLHeadingElement[] { |   private findTocHeadings(docElement: Element): HTMLHeadingElement[] { | ||||||
|     const headings = docElement.querySelectorAll('h2,h3'); |     const headings = docElement.querySelectorAll('h1,h2,h3'); | ||||||
|     const skipNoTocHeadings = (heading: HTMLHeadingElement) => !/(?:no-toc|notoc)/i.test(heading.className); |     const skipNoTocHeadings = (heading: HTMLHeadingElement) => !/(?:no-toc|notoc)/i.test(heading.className); | ||||||
| 
 | 
 | ||||||
|     return Array.prototype.filter.call(headings, skipNoTocHeadings); |     return Array.prototype.filter.call(headings, skipNoTocHeadings); | ||||||
|  | |||||||
| @ -6,11 +6,6 @@ | |||||||
|   bottom: 32px; |   bottom: 32px; | ||||||
|   overflow-y: auto; |   overflow-y: auto; | ||||||
|   overflow-x: hidden; |   overflow-x: hidden; | ||||||
| 
 |  | ||||||
|   @media (max-width: 800px) { |  | ||||||
|     display: none; |  | ||||||
|     width: 0; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| aio-toc { | aio-toc { | ||||||
| @ -19,16 +14,9 @@ aio-toc { | |||||||
|       display: none; |       display: none; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   &:not(.embedded) { |  | ||||||
|     @media (max-width: 800px) { |  | ||||||
|       display: none; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } | .toc-inner { | ||||||
| 
 |  | ||||||
| aio-toc > div { |  | ||||||
|   border-left: 4px solid #4285f4; |   border-left: 4px solid #4285f4; | ||||||
|   font-size: 13px; |   font-size: 13px; | ||||||
|   overflow-y: visible; |   overflow-y: visible; | ||||||
| @ -59,6 +47,7 @@ aio-toc > div { | |||||||
|     border: none; |     border: none; | ||||||
|     box-shadow: none; |     box-shadow: none; | ||||||
|     padding: 0; |     padding: 0; | ||||||
|  |     text-align: start; | ||||||
| 
 | 
 | ||||||
|     &:focus.embedded { |     &:focus.embedded { | ||||||
|       outline: none; |       outline: none; | ||||||
| @ -67,7 +56,8 @@ aio-toc > div { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   button.toc-heading, |   button.toc-heading, | ||||||
|   div.toc-heading { |   div.toc-heading, | ||||||
|  |   ul.toc-list li.h1 { | ||||||
|     font-size: 115%; |     font-size: 115%; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -131,10 +121,10 @@ aio-toc > div { | |||||||
|     margin: 6px 0; |     margin: 6px 0; | ||||||
|     padding: 0; |     padding: 0; | ||||||
|     position: relative; |     position: relative; | ||||||
|     transition: all 0.3 ease-in-out; |     transition: all 0.3s ease-in-out; | ||||||
| 
 | 
 | ||||||
|     a { |     a { | ||||||
|       font-size: 12px; |       font-size: inherit; | ||||||
|       color: lighten($darkgray, 10); |       color: lighten($darkgray, 10); | ||||||
|       display:table-cell; |       display:table-cell; | ||||||
|       overflow: visible; |       overflow: visible; | ||||||
| @ -154,6 +144,17 @@ aio-toc > div { | |||||||
|   ul.toc-list li.h3 { |   ul.toc-list li.h3 { | ||||||
|     margin-left: 15px; |     margin-left: 15px; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   ul.toc-list li.h1:after { | ||||||
|  |     content: ""; | ||||||
|  |     display: block; | ||||||
|  |     height: 1px; | ||||||
|  |     width: 40%; | ||||||
|  |     margin: 24px 0px 10px; | ||||||
|  |     background: #DBDBDB; | ||||||
|  |     clear: both; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| aio-toc.embedded > div.collapsed li.secondary { | aio-toc.embedded > div.collapsed li.secondary { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user