| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,20 +6,20 @@ const path = require('path'); | |||
| 6 | 6 | module.exports = function (config) { | |
| 7 | 7 | config.set({ | |
| 8 | 8 | basePath: '', | |
| 9 | - frameworks: ['jasmine', 'angular-cli'], | ||
| 9 | + frameworks: ['jasmine', '@angular/cli'], | ||
| 10 | 10 | plugins: [ | |
| 11 | 11 | require('karma-jasmine'), | |
| 12 | 12 | require('karma-chrome-launcher'), | |
| 13 | 13 | require('karma-remap-istanbul'), | |
| 14 | - require('angular-cli/plugins/karma'), | ||
| 14 | + require('@angular/cli/plugins/karma'), | ||
| 15 | 15 | require('karma-browserstack-launcher'), | |
| 16 | 16 | require('karma-sauce-launcher'), | |
| 17 | 17 | ], | |
| 18 | 18 | files: [ | |
| 19 | 19 | { pattern: './src/test.ts', watched: false } | |
| 20 | 20 | ], | |
| 21 | 21 | preprocessors: { | |
| 22 | - './src/test.ts': ['angular-cli'] | ||
| 22 | + './src/test.ts': ['@angular/cli'] | ||
| 23 | 23 | }, | |
| 24 | 24 | mime: { | |
| 25 | 25 | 'text/x-typescript': ['ts','tsx'] | |
@@ -66,7 +66,7 @@ module.exports = function (config) { | |||
| 66 | 66 | pollingTimeout: 20000 | |
| 67 | 67 | }, | |
| 68 | 68 | }); | |
| 69 | - | ||
| 69 | + | ||
| 70 | 70 | if (process.env['TRAVIS']) { | |
| 71 | 71 | ||
| 72 | 72 | let buildId = `TRAVIS #${process.env.TRAVIS_BUILD_NUMBER} (${process.env.TRAVIS_BUILD_ID})`; | |
@@ -87,5 +87,5 @@ module.exports = function (config) { | |||
| 87 | 87 | } | |
| 88 | 88 | ||
| 89 | 89 | config.browsers = platformMap[platformType]; | |
| 90 | - } | ||
| 90 | + } | ||
| 91 | 91 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1 +1,2 @@ | |||
| 1 | 1 | export * from './navbar'; | |
| 2 | + export * from './searchbar'; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ | |||
| 8 | 8 | </a> | |
| 9 | 9 | <a md-button class="docs-button" routerLink="components">Components</a> | |
| 10 | 10 | <a md-button class="docs-button" routerLink="guides">Guides</a> | |
| 11 | + <search-bar-component></search-bar-component> | ||
| 11 | 12 | <a md-button class="docs-button" href="https://github.com/angular/material2" aria-label="GitHub Repository"> | |
| 12 | 13 | <img class="docs-github-logo" | |
| 13 | 14 | src="../../../assets/img/homepage/github-circle-white-transparent.svg" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,12 +2,6 @@ | |||
| 2 | 2 | display: flex; | |
| 3 | 3 | flex-wrap: wrap; | |
| 4 | 4 | padding: 8px 16px; | |
| 5 | - | ||
| 6 | - > .mat-button { | ||
| 7 | - &:last-child { | ||
| 8 | - margin-left: auto; | ||
| 9 | - } | ||
| 10 | - } | ||
| 11 | 5 | } | |
| 12 | 6 | ||
| 13 | 7 | .docs-angular-logo { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + export * from './searchbar'; | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,17 @@ | |||
| 1 | + <div class="docs-search-input-container"> | ||
| 2 | + <input | ||
| 3 | + placeholder="Search" | ||
| 4 | + type="text" | ||
| 5 | + (focus)="toggleIsExpanded(true)" | ||
| 6 | + (blur)="toggleIsExpanded(false)" | ||
| 7 | + (keyup.enter)="handlePlainSearch($event.target.value.toLowerCase())" | ||
| 8 | + [mdAutocomplete]="auto" | ||
| 9 | + [formControl]="searchControl"> | ||
| 10 | + <i class="material-icons">search</i> | ||
| 11 | + </div> | ||
| 12 | + | ||
| 13 | + <md-autocomplete #auto="mdAutocomplete" [displayWith]="displayFn"> | ||
| 14 | + <md-option *ngFor="let item of filteredSuggestions | async" [value]="item"> | ||
| 15 | + {{item.name}} | ||
| 16 | + </md-option> | ||
| 17 | + </md-autocomplete> | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,60 @@ | |||
| 1 | + $input-bg: #85D9E2; | ||
| 2 | + | ||
| 3 | + @mixin color-placeholder() { | ||
| 4 | + -webkit-font-smoothing: antialiased; | ||
| 5 | + color: white; | ||
| 6 | + } | ||
| 7 | + | ||
| 8 | + :host { | ||
| 9 | + position: relative; | ||
| 10 | + flex: 2; | ||
| 11 | + | ||
| 12 | + * { | ||
| 13 | + box-sizing: border-box; | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + &.docs-expanded .docs-search-input-container { | ||
| 17 | + width: 100%; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + .docs-search-input-container { | ||
| 21 | + display: block; | ||
| 22 | + position: relative; | ||
| 23 | + margin-left: auto; | ||
| 24 | + height: 100%; | ||
| 25 | + width: 200px; | ||
| 26 | + transition: width .2s ease; | ||
| 27 | + i.material-icons { | ||
| 28 | + position: absolute; | ||
| 29 | + left: 15px; top: 50%; | ||
| 30 | + transform: translateY(-50%); | ||
| 31 | + height: 28px; | ||
| 32 | + width: 28px; | ||
| 33 | + } | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + input { | ||
| 37 | + background: $input-bg; | ||
| 38 | + border: none; | ||
| 39 | + border-radius: 2px; | ||
| 40 | + color: white; | ||
| 41 | + font-size: 18px; | ||
| 42 | + height: 95%; | ||
| 43 | + line-height: 95%; | ||
| 44 | + padding-left: 50px; | ||
| 45 | + position: relative; | ||
| 46 | + transition: width .2s ease; | ||
| 47 | + width: 100%; | ||
| 48 | + | ||
| 49 | + /* Set placeholder text to be white */ | ||
| 50 | + &::-webkit-input-placeholder { @include color-placeholder(); } /* Chrome/Opera/Safari */ | ||
| 51 | + &::-moz-placeholder { @include color-placeholder(); } /* Firefox 19+ */ | ||
| 52 | + &:-moz-placeholder { @include color-placeholder(); } /* Firefox 18- */ | ||
| 53 | + &:ms-input-placeholder { @include color-placeholder(); } /* IE 10+ */ | ||
| 54 | + | ||
| 55 | + &:focus { | ||
| 56 | + outline: none; | ||
| 57 | + } | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,99 @@ | |||
| 1 | + import {Injectable} from '@angular/core'; | ||
| 2 | + import {TestBed, inject, async, ComponentFixture} from '@angular/core/testing'; | ||
| 3 | + import {Router, RouterModule} from '@angular/router'; | ||
| 4 | + import {MaterialModule} from '@angular/material'; | ||
| 5 | + import {ReactiveFormsModule, FormControl} from '@angular/forms'; | ||
| 6 | + | ||
| 7 | + import {DocumentationItems, DocItem} from '../../documentation-items/documentation-items'; | ||
| 8 | + import {SearchBar} './searchbar'; | ||
| 9 | + | ||
| 10 | + const mockRouter = { | ||
| 11 | + navigate: jasmine.createSpy('navigate'), | ||
| 12 | + navigateByUrl: jasmine.createSpy('navigateByUrl'); | ||
| 13 | + }; | ||
| 14 | + | ||
| 15 | + const testDocItem = { | ||
| 16 | + id: 'test-doc-item', | ||
| 17 | + name: 'TestingExample', | ||
| 18 | + examples: ['test-examples'] | ||
| 19 | + }; | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + describe('SearchBar', () => { | ||
| 23 | + let fixture: ComponentFixture<SearchBar>; | ||
| 24 | + let component: SearchBar; | ||
| 25 | + | ||
| 26 | + beforeEach(async(() => { | ||
| 27 | + TestBed.configureTestingModule({ | ||
| 28 | + imports: [RouterModule, ReactiveFormsModule, MaterialModule], | ||
| 29 | + declarations: [SearchBar], | ||
| 30 | + providers: [ | ||
| 31 | + {provide: DocumentationItems, useClass: MockDocumentationItems}, | ||
| 32 | + {provide: Router, useValue: mockRouter}, | ||
| 33 | + ], | ||
| 34 | + }); | ||
| 35 | + | ||
| 36 | + TestBed.compileComponents(); | ||
| 37 | + fixture = TestBed.createComponent(SearchBar); | ||
| 38 | + component = fixture.componentInstance; | ||
| 39 | + component.searchControl = new FormControl(''); | ||
| 40 | + fixture.detectChanges(); | ||
| 41 | + })); | ||
| 42 | + | ||
| 43 | + afterEach(() => { | ||
| 44 | + component._router.navigateByUrl.calls.reset(); | ||
| 45 | + }); | ||
| 46 | + | ||
| 47 | + it('should toggle isExpanded', () => { | ||
| 48 | + expect(component._isExpanded).toBe(false); | ||
| 49 | + component.toggleIsExpanded(); | ||
| 50 | + expect(component._isExpanded).toBe(true); | ||
| 51 | + }); | ||
| 52 | + | ||
| 53 | + describe('Filter Search Suggestions', () => { | ||
| 54 | + it('should return all items matching search query', () => { | ||
| 55 | + const query = 'testing'; | ||
| 56 | + const result = component.filterSearchSuggestions(query); | ||
| 57 | + expect(result).toEqual([testDocItem]); | ||
| 58 | + }); | ||
| 59 | + | ||
| 60 | + it('should return empty list if no items match', () => { | ||
| 61 | + const query = 'does not exist'; | ||
| 62 | + const result = component.filterSearchSuggestions(query); | ||
| 63 | + expect(result).toEqual([]); | ||
| 64 | + }); | ||
| 65 | + }); | ||
| 66 | + | ||
| 67 | + describe('Navigate', () => { | ||
| 68 | + | ||
| 69 | + it('should take an id and navigate to the given route', () => { | ||
| 70 | + component.navigate('button-toggle'); | ||
| 71 | + expect(component._router.navigateByUrl).toHaveBeenCalled(); | ||
| 72 | + }); | ||
| 73 | + | ||
| 74 | + it('should not navigate if no id is given', () => { | ||
| 75 | + component.navigate(''); | ||
| 76 | + expect(component._router.navigateByUrl).not.toHaveBeenCalled(); | ||
| 77 | + }); | ||
| 78 | + }); | ||
| 79 | + | ||
| 80 | + it('should show a snackbar error', () => { | ||
| 81 | + spyOn(component._snackBar, 'open'); | ||
| 82 | + component._showError(); | ||
| 83 | + expect(component._snackBar.open).toHaveBeenCalled(); | ||
| 84 | + expect(component._snackBar.open).toHaveBeenCalledWith( | ||
| 85 | + 'No search results found.', | ||
| 86 | + null, {duration: 3000}); | ||
| 87 | + }); | ||
| 88 | + | ||
| 89 | + it('should return the proper display value for form control', () => { | ||
| 90 | + const result = component.displayFn(testDocItem); | ||
| 91 | + expect(result).toEqual(testDocItem.name); | ||
| 92 | + }); | ||
| 93 | + }); | ||
| 94 | + | ||
| 95 | + | ||
| 96 | + class MockDocumentationItems extends DocumentationItems { | ||
| 97 | + getAllItems(): DocItem[] { return [testDocItem]; } | ||
| 98 | + } | ||
| 99 | + | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,80 @@ | |||
| 1 | + import {Component, ViewChild} from '@angular/core'; | ||
| 2 | + import {MdAutocompleteTrigger, MdSnackBar} from '@angular/material'; | ||
| 3 | + import {Router} from '@angular/router'; | ||
| 4 | + import {FormControl} from '@angular/forms'; | ||
| 5 | + import {Observable} from 'rxjs/Observable'; | ||
| 6 | + import 'rxjs/add/operator/mergeMap'; | ||
| 7 | + | ||
| 8 | + import {DocumentationItems, DocItem} from '../../documentation-items/documentation-items'; | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + @Component({ | ||
| 12 | + selector: 'search-bar-component', | ||
| 13 | + templateUrl: './searchbar.html', | ||
| 14 | + styleUrls: ['./searchbar.scss'], | ||
| 15 | + host: { | ||
| 16 | + '[class.docs-expanded]': '_isExpanded' | ||
| 17 | + } | ||
| 18 | + }) | ||
| 19 | + | ||
| 20 | + export class SearchBar { | ||
| 21 | + | ||
| 22 | + @ViewChild(MdAutocompleteTrigger) | ||
| 23 | + private _autocompleteTrigger: MdAutocompleteTrigger; | ||
| 24 | + | ||
| 25 | + public allDocItems: DocItem[]; | ||
| 26 | + public filteredSuggestions: Observable<DocItem[]>; | ||
| 27 | + public searchControl: FormControl = new FormControl(''); | ||
| 28 | + public sub; | ||
| 29 | + | ||
| 30 | + private _isExpanded: boolean = false; | ||
| 31 | + | ||
| 32 | + constructor( | ||
| 33 | + private _docItems: DocumentationItems, | ||
| 34 | + private _router: Router, | ||
| 35 | + private _snackBar: MdSnackBar | ||
| 36 | + ) { | ||
| 37 | + this.allDocItems = _docItems.getAllItems(); | ||
| 38 | + this.filteredSuggestions = this.searchControl.valueChanges | ||
| 39 | + .startWith(null) | ||
| 40 | + .map(item => item ? this.filterSearchSuggestions(item) : this.allDocItems.slice()); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + // This handles the user interacting with the autocomplete panel clicks or keyboard. | ||
| 44 | + public ngAfterViewInit() { | ||
| 45 | + // We listen to the changes on `filteredSuggestions in order to | ||
| 46 | + // listen to the latest _autocompleteTrigger.optionSelections | ||
| 47 | + this.sub = this.filteredSuggestions | ||
| 48 | + .flatMap(_ => Observable.merge(...this._autocompleteTrigger.optionSelections)) | ||
| 49 | + .subscribe(evt => this.navigate(evt.source.value.id)); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + public ngOnDestroy() { | ||
| 53 | + if (this.sub) { this.sub.unsubscribe(); } | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + public toggleIsExpanded() { | ||
| 57 | + this._isExpanded = !this._isExpanded; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + public displayFn(item: DocItem) { | ||
| 61 | + return item.name; | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + public filterSearchSuggestions(searchTerm): DocItem[] { | ||
| 65 | + return this.allDocItems.filter(item => new RegExp(`^${searchTerm}`, 'gi').test(item.name)); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + public handlePlainSearch(searchTerm) { | ||
| 69 | + const item = this.allDocItems.find(item => item.name.toLowerCase() === searchTerm); | ||
| 70 | + item ? this.navigate(item.id) : this._showError(); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + public navigate(id) { | ||
| 74 | + return id ? this._router.navigateByUrl(`/components/component/${id}`) : null; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + private _showError() { | ||
| 78 | + this._snackBar.open('No search results found.', null, {duration: 3000}); | ||
| 79 | + } | ||
| 80 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,9 +1,11 @@ | |||
| 1 | 1 | import {NgModule} from '@angular/core'; | |
| 2 | 2 | import {HttpModule} from '@angular/http'; | |
| 3 | + import {ReactiveFormsModule} from '@angular/forms'; | ||
| 3 | 4 | import {DocViewer} from './doc-viewer/doc-viewer'; | |
| 4 | 5 | import {ExampleViewer} from './example-viewer/example-viewer'; | |
| 5 | 6 | import {DocumentationItems} from './documentation-items/documentation-items'; | |
| 6 | 7 | import {NavBar} from './navbar/navbar'; | |
| 8 | + import {SearchBar} from './navbar/searchbar/searchbar'; | ||
| 7 | 9 | import {MaterialModule} from '@angular/material'; | |
| 8 | 10 | import {BrowserModule} from '@angular/platform-browser'; | |
| 9 | 11 | import {RouterModule} from '@angular/router'; | |
@@ -16,9 +18,10 @@ import {GuideItems} from './guide-items/guide-items'; | |||
| 16 | 18 | HttpModule, | |
| 17 | 19 | RouterModule, | |
| 18 | 20 | BrowserModule, | |
| 21 | + ReactiveFormsModule, | ||
| 19 | 22 | MaterialModule, | |
| 20 | 23 | ], | |
| 21 | - declarations: [DocViewer, ExampleViewer, NavBar, PlunkerButton], | ||
| 24 | + declarations: [DocViewer, ExampleViewer, NavBar, SearchBar, PlunkerButton], | ||
| 22 | 25 | exports: [DocViewer, ExampleViewer, NavBar, PlunkerButton], | |
| 23 | 26 | providers: [DocumentationItems, GuideItems], | |
| 24 | 27 | entryComponents: [ | |
| Back | FazBrowse Home | New Git URL |
0 commit comments