diff --git a/aio/content/guide/animations.md b/aio/content/guide/animations.md
index 1928446ae0..5f8a75b0a9 100644
--- a/aio/content/guide/animations.md
+++ b/aio/content/guide/animations.md
@@ -1,17 +1,12 @@
-@title
-Animations
-
-@intro
-A guide to Angular's animation system.
-
-@description
-
+# Animations
Motion is an important aspect in the design of modern web applications. Good
user interfaces transition smoothly between states with engaging animations
that call attention where it's needed. Well-designed animations can make a UI not only
more fun but also easier to use.
+## Overview
+
Angular's animation system lets you build animations that run with the same kind of native
performance found in pure CSS animations. You can also tightly integrate your
animation logic with the rest of your application code, for ease of control.
@@ -33,7 +28,7 @@ add it to your page.
-
+
**NgModules** help organize an application into cohesive blocks of functionality.
@@ -25,32 +18,23 @@ of creating and maintaining a single root `AppModule` for the entire application
This page covers NgModules in greater depth.
-## Table of Contents
-
-
+
* [Angular modularity](guide/ngmodule#angular-modularity "Add structure to the app with NgModule")
* [The application root module](guide/ngmodule#root-module "The startup module that every app requires")
-* [Bootstrap](guide/ngmodule#bootstrap "Launch the app in a browser with the root module as the entry point") the root module
+* [Bootstrap the root module](guide/ngmodule#bootstrap "Launch the app in a browser with the root module as the entry point")
* [Declarations](guide/ngmodule#declarations "Declare the components, directives, and pipes that belong to a module")
* [Providers](guide/ngmodule#providers "Extend the app with additional services")
* [Imports](guide/ngmodule#imports "Import components, directives, and pipes for use in component templates")
* [Resolve conflicts](guide/ngmodule#resolve-conflicts "When two directives have the same selector")
-
* [Feature modules](guide/ngmodule#feature-modules "Partition the app into feature modules")
-* [Lazy loaded modules](guide/ngmodule#lazy-load "Load modules asynchronously") with the router
+* [Lazy loaded modules with the router](guide/ngmodule#lazy-load "Load modules asynchronously")
* [Shared modules](guide/ngmodule#shared-module "Create modules for commonly used components, directives, and pipes")
* [The Core module](guide/ngmodule#core-module "Create a core module with app-wide singleton services and single-use components")
* [Configure core services with _forRoot_](guide/ngmodule#core-for-root "Configure providers during module import")
-* [Prevent reimport of the _CoreModule_](guide/ngmodule#prevent-reimport "because bad things happen if a lazy loaded module imports Core")
+* [Prevent re-import of the _CoreModule_](guide/ngmodule#prevent-reimport "because bad things happen if a lazy loaded module imports Core")
* [NgModule metadata properties](guide/ngmodule#ngmodule-properties "A technical summary of the @NgModule metadata properties")
+
diff --git a/aio/content/guide/quickstart.md b/aio/content/guide/quickstart.md
index 3350ea057e..27e8feef6b 100644
--- a/aio/content/guide/quickstart.md
+++ b/aio/content/guide/quickstart.md
@@ -1,12 +1,7 @@
-@title
-QuickStart
-
-@description
-
-
+
QuickStart
Angular applications are made up of _components_.
- A _component_ is the combination of an HTML template and a component class that controls a portion of the screen. Here is an example of a component that displays a simple string:
+A _component_ is the combination of an HTML template and a component class that controls a portion of the screen. Here is an example of a component that displays a simple string:
diff --git a/aio/content/marketing/about.html b/aio/content/marketing/about.html
index 8e15f8dd02..4039734d1a 100644
--- a/aio/content/marketing/about.html
+++ b/aio/content/marketing/about.html
@@ -1,8 +1,8 @@
-
Angular Contributors
+
Angular Contributors
Building For the Future
Angular is built by a team of engineers who share a passion for
making web development feel effortless. We believe that writing
beautiful apps should be joyful and fun. We're building a
platform for the future.
-
\ No newline at end of file
+
diff --git a/aio/content/marketing/api.html b/aio/content/marketing/api.html
index 83743a1a58..c2508b53b3 100755
--- a/aio/content/marketing/api.html
+++ b/aio/content/marketing/api.html
@@ -1,2 +1,2 @@
-
API List
+
API List
diff --git a/aio/content/marketing/contribute.md b/aio/content/marketing/contribute.md
index 589f045f0d..9e9d1c05e6 100644
--- a/aio/content/marketing/contribute.md
+++ b/aio/content/marketing/contribute.md
@@ -1,10 +1,5 @@
-@title
-Contribute
+# Contribute to Angular
-@intro
-Contribute to Angular
-
-@description
Help us build the framework of the future!
## Angular Projects
diff --git a/aio/content/marketing/events.html b/aio/content/marketing/events.html
index 0214c01d4c..b491a33fd4 100755
--- a/aio/content/marketing/events.html
+++ b/aio/content/marketing/events.html
@@ -1,4 +1,5 @@
-
Create desktop-installed apps across Mac, Windows, and Linux using the same Angular methods you've learned for the web plus the ability to access native OS APIs.
-
+
Speed and Performance
@@ -43,7 +44,7 @@
Productivity
-
+
Templates
Quickly create UI views with simple and powerful template syntax.
@@ -53,7 +54,7 @@
Angular CLI
Command line tools: start building fast, add components and tests, then instantly deploy.
-
+
IDEs
Get intelligent code completion, instant errors, and other feedback in popular editors and IDEs.
`;
- component.currentDoc = { title: 'fake title', contents, id: 'a/b' };
+ setCurrentDoc(contents);
// necessary to trigger Bar's projection within ngOnInit
fixture.detectChanges();
@@ -298,4 +318,86 @@ describe('DocViewerComponent', () => {
'expected 2nd Baz template content');
});
+
+ describe('Title', () => {
+ let titleService: TestTitleService;
+
+ beforeEach(() => {
+ titleService = TestBed.get(Title);
+ });
+
+ it('should set the default empty title when no
', () => {
+ setCurrentDoc('Some content');
+ fixture.detectChanges();
+ expect(titleService.setTitle).toHaveBeenCalledWith('Angular');
+ });
+
+ it('should set the expected title when has
', () => {
+ setCurrentDoc('
Features
Some content');
+ fixture.detectChanges();
+ expect(titleService.setTitle).toHaveBeenCalledWith('Angular - Features');
+ });
+
+ it('should set the expected title with a no-toc
Some content');
+ fixture.detectChanges();
+ });
+
+ it('should add ', () => {
+ expect(getAioToc()).toBeTruthy();
+ });
+
+ it('should have with "embedded" class', () => {
+ expect(getAioToc().classList.contains('embedded')).toEqual(true);
+ });
+
+ it('should call Toc Service genToc()', () => {
+ expect(tocService.genToc).toHaveBeenCalled();
+ });
+ });
+ });
});
diff --git a/aio/src/app/layout/doc-viewer/doc-viewer.component.ts b/aio/src/app/layout/doc-viewer/doc-viewer.component.ts
index 14a8ae53e6..504f3c3648 100644
--- a/aio/src/app/layout/doc-viewer/doc-viewer.component.ts
+++ b/aio/src/app/layout/doc-viewer/doc-viewer.component.ts
@@ -6,6 +6,8 @@ import {
import { EmbeddedComponents } from 'app/embedded/embedded.module';
import { DocumentContents } from 'app/documents/document.service';
+import { Title } from '@angular/platform-browser';
+import { TocService } from 'app/shared/toc.service';
interface EmbeddedComponentFactory {
contentPropertyName: string;
@@ -18,13 +20,7 @@ const initialDocViewerContent = initialDocViewerElement ? initialDocViewerElemen
@Component({
selector: 'aio-doc-viewer',
- template: '',
- styles: [ `
- :host >>> doc-title.not-found h1 {
- color: white;
- background-color: red;
- }
- `]
+ template: ''
// TODO(robwormald): shadow DOM and emulated don't work here (?!)
// encapsulation: ViewEncapsulation.Native
})
@@ -41,7 +37,9 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
componentFactoryResolver: ComponentFactoryResolver,
elementRef: ElementRef,
embeddedComponents: EmbeddedComponents,
- private injector: Injector
+ private injector: Injector,
+ private titleService: Title,
+ private tocService: TocService
) {
this.hostElement = elementRef.nativeElement;
// Security: the initialDocViewerContent comes from the prerendered DOM and is considered to be secure
@@ -77,6 +75,8 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
if (!doc.contents) { return; }
+ this.addTitleAndToc(doc.id);
+
// TODO(i): why can't I use for-of? why doesn't typescript like Map#value() iterators?
this.embeddedComponentFactories.forEach(({ contentPropertyName, factory }, selector) => {
const embeddedComponentElements = this.hostElement.querySelectorAll(selector);
@@ -92,8 +92,27 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
});
}
+ private addTitleAndToc(docId: string) {
+ this.tocService.reset();
+ let title = '';
+ const titleEl = this.hostElement.querySelector('h1');
+ // Only create TOC for docs with an
title
+ // If you don't want a TOC, don't have an
+ if (titleEl) {
+ title = titleEl.innerText.trim();
+ if (!/(no-toc|notoc)/i.test(titleEl.className)) {
+ this.tocService.genToc(this.hostElement, docId);
+ titleEl.insertAdjacentHTML('afterend', '');
+ }
+ }
+ this.titleService.setTitle(title ? `Angular - ${title}` : 'Angular');
+ }
+
ngDoCheck() {
- if (this.displayedDoc) { this.displayedDoc.detectChanges(); }
+ // TODO: make sure this isn't called too often on the same doc
+ if (this.displayedDoc) {
+ this.displayedDoc.detectChanges();
+ }
}
ngOnDestroy() {
diff --git a/aio/src/app/shared/toc.service.spec.ts b/aio/src/app/shared/toc.service.spec.ts
new file mode 100644
index 0000000000..780243f6df
--- /dev/null
+++ b/aio/src/app/shared/toc.service.spec.ts
@@ -0,0 +1,227 @@
+import { ReflectiveInjector, SecurityContext } from '@angular/core';
+import { DOCUMENT, DomSanitizer, SafeHtml } from '@angular/platform-browser';
+
+import { TocItem, TocService } from './toc.service';
+
+describe('TocService', () => {
+ let injector: ReflectiveInjector;
+ let tocService: TocService;
+
+ // call TocService.genToc
+ function callGenToc(html = '', docId = 'fizz/buzz'): HTMLDivElement {
+ const el = document.createElement('div');
+ el.innerHTML = html;
+ tocService.genToc(el, docId);
+ return el;
+ }
+
+ beforeEach(() => {
+ injector = ReflectiveInjector.resolveAndCreate([
+ { provide: DomSanitizer, useClass: TestDomSanitizer },
+ { provide: DOCUMENT, useValue: document },
+ TocService,
+ ]);
+ tocService = injector.get(TocService);
+ });
+
+ it('should be creatable', () => {
+ expect(tocService).toBeTruthy();
+ });
+
+ describe('should clear tocList', () => {
+ // Start w/ dummy data from previous usage
+ beforeEach(() => tocService.tocList = [{}, {}] as TocItem[]);
+
+ it('when reset()', () => {
+ tocService.reset();
+ expect(tocService.tocList.length).toEqual(0);
+ });
+
+ it('when given undefined doc element', () => {
+ tocService.genToc(undefined);
+ expect(tocService.tocList.length).toEqual(0);
+ });
+
+ it('when given doc element w/ no headings', () => {
+ callGenToc('
This
and
that
');
+ expect(tocService.tocList.length).toEqual(0);
+ });
+
+ it('when given doc element w/ headings other than h2 & h3', () => {
+ callGenToc('
This
and
that
');
+ expect(tocService.tocList.length).toEqual(0);
+ });
+
+ it('when given doc element w/ no-toc headings', () => {
+ // tolerates different spellings/casing of the no-toc class
+ callGenToc(`
+
one
some one
+
two
some two
+
three
some three
+
four
some four
+ `);
+ expect(tocService.tocList.length).toEqual(0);
+ });
+ });
+
+ describe('when given many headings', () => {
+ let docId: string;
+ let docEl: HTMLDivElement;
+ let tocList: TocItem[];
+ let headings: NodeListOf;
+
+ beforeEach(() => {
+ docId = 'fizz/buzz';
+
+ docEl = callGenToc(`
+
Fun with TOC
+
+
Heading one
+
h2 toc 0
+
+
H2 Two
+
h2 toc 1
+
+
H2 Three
+
h2 toc 2
+
H3 3a
h3 toc 3
+
H3 3b
h3 toc 4
+
+
+
H4 of h3-3b
an h4
+
+
H2 4 repeat
+
h2 toc 5
+
+
H2 4 repeat
+
h2 toc 6
+
+
Skippy
+
Skip this header
+
+
H2 6
+
h2 toc 7
+
H3 6a
h3 toc 8
+ `, docId);
+
+ tocList = tocService.tocList;
+ headings = docEl.querySelectorAll('h1,h2,h3,h4') as NodeListOf;
+ });
+
+ it('should have tocList with expect number of TocItems', () => {
+ // should ignore h1, h4, and the no-toc h2
+ expect(tocList.length).toEqual(headings.length - 3);
+ });
+
+ it('should have href with docId and heading\'s id', () => {
+ const tocItem = tocList[0];
+ expect(tocItem.href).toEqual(`${docId}#heading-one-special-id`);
+ });
+
+ it('should have level "h2" for an
', () => {
+ const tocItem = tocList[0];
+ expect(tocItem.level).toEqual('h2');
+ });
+
+ it('should have level "h3" for an
', () => {
+ const tocItem = tocList[3];
+ expect(tocItem.level).toEqual('h3');
+ });
+
+ it('should have title which is heading\'s innerText ', () => {
+ const heading = headings[3];
+ const tocItem = tocList[2];
+ expect(heading.innerText).toEqual(tocItem.title);
+ });
+
+ it('should have "SafeHtml" content which is heading\'s innerHTML ', () => {
+ const heading = headings[3];
+ const content = tocList[2].content;
+ expect((content).changingThisBreaksApplicationSecurity)
+ .toEqual(heading.innerHTML);
+ });
+
+ it('should calculate and set id of heading without an id', () => {
+ const id = headings[2].getAttribute('id');
+ expect(id).toEqual('h2-two');
+ });
+
+ it('should have href with docId and calculated heading id', () => {
+ const tocItem = tocList[1];
+ expect(tocItem.href).toEqual(`${docId}#h2-two`);
+ });
+
+ it('should ignore HTML in heading when calculating id', () => {
+ const id = headings[3].getAttribute('id');
+ const tocItem = tocList[2];
+ expect(id).toEqual('h2-three', 'heading id');
+ expect(tocItem.href).toEqual(`${docId}#h2-three`, 'tocItem href');
+ });
+
+ it('should avoid repeating an id when calculating', () => {
+ const tocItem4a = tocList[5];
+ const tocItem4b = tocList[6];
+ expect(tocItem4a.href).toEqual(`${docId}#h2-4-repeat`, 'first');
+ expect(tocItem4b.href).toEqual(`${docId}#h2-4-repeat-2`, 'second');
+ });
+ });
+
+ describe('TocItem for an h2 with anchor link 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(`
+