feat(aio): add code-example and code-tabs
* move embedded components to EmbeddedModule * add PrettyPrint service; load pretty print js dynamically * make code-example to syntax highlighting w/ `prettyPrintOne` * add code-tabs * Implement copy code button
This commit is contained in:
parent
2e4fe7fd2e
commit
837ed788f4
|
@ -34,8 +34,7 @@ describe('site App', function() {
|
|||
it('should render `{@example}` dgeni tags as `<code-example>` elements with HTML escaped content', () => {
|
||||
page.navigateTo('guide/component-styles');
|
||||
const codeExample = element.all(by.css('code-example')).first();
|
||||
expect(page.getInnerHtml(codeExample))
|
||||
.toContain('@Component({\n selector: \'hero-app\',\n template: `\n <h1>Tour of Heroes</h1>');
|
||||
expect(page.getInnerHtml(codeExample)).toContain('<h1>Tour of Heroes</h1>');
|
||||
});
|
||||
|
||||
describe('api-docs', () => {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common';
|
||||
|
||||
|
@ -9,6 +10,7 @@ import { MdButtonModule} from '@angular/material/button';
|
|||
import { MdIconModule} from '@angular/material/icon';
|
||||
import { MdInputModule } from '@angular/material/input';
|
||||
import { MdSidenavModule } from '@angular/material/sidenav';
|
||||
import { MdTabsModule } from '@angular/material';
|
||||
import { Platform } from '@angular/material/core';
|
||||
|
||||
// Temporary fix for MdSidenavModule issue:
|
||||
|
@ -18,7 +20,7 @@ import 'rxjs/add/operator/first';
|
|||
import { AppComponent } from 'app/app.component';
|
||||
import { ApiService } from 'app/embedded/api/api.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { embeddedComponents, EmbeddedComponents } from 'app/embedded';
|
||||
import { EmbeddedModule } from 'app/embedded/embedded.module';
|
||||
import { GaService } from 'app/shared/ga.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
|
@ -35,16 +37,18 @@ import { AutoScrollService } from 'app/shared/auto-scroll.service';
|
|||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
EmbeddedModule,
|
||||
HttpModule,
|
||||
BrowserAnimationsModule,
|
||||
MdButtonModule,
|
||||
MdIconModule,
|
||||
MdInputModule,
|
||||
MdToolbarModule,
|
||||
MdSidenavModule
|
||||
MdSidenavModule,
|
||||
MdTabsModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
embeddedComponents,
|
||||
DocViewerComponent,
|
||||
TopMenuComponent,
|
||||
NavMenuComponent,
|
||||
|
@ -54,7 +58,6 @@ import { AutoScrollService } from 'app/shared/auto-scroll.service';
|
|||
],
|
||||
providers: [
|
||||
ApiService,
|
||||
EmbeddedComponents,
|
||||
GaService,
|
||||
Logger,
|
||||
Location,
|
||||
|
@ -66,7 +69,6 @@ import { AutoScrollService } from 'app/shared/auto-scroll.service';
|
|||
Platform,
|
||||
AutoScrollService,
|
||||
],
|
||||
entryComponents: [ embeddedComponents ],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/* tslint:disable:no-unused-variable */
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
import { CodeExampleComponent } from './code-example.component';
|
||||
|
||||
describe('CodeExampleComponent', () => {
|
||||
let component: CodeExampleComponent;
|
||||
let fixture: ComponentFixture<CodeExampleComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CodeExampleComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CodeExampleComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,73 +0,0 @@
|
|||
/* tslint:disable component-selector */
|
||||
|
||||
import { Component, OnInit, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
|
||||
|
||||
// TODO(i): add clipboard copy functionality
|
||||
|
||||
/**
|
||||
* Angular.io Code Example
|
||||
*
|
||||
* Pretty renders a code block, primarily used in the docs and API reference. Can be used within an Angular app, or
|
||||
* independently, provided that it is dynamically generated by the component resolver.
|
||||
*
|
||||
* Usage:
|
||||
* <code-example [language]="..." [escape]="..." [format]="..." [showcase]="..." [animated]="...">
|
||||
* console.log('Hello World')
|
||||
* </code-example>
|
||||
*/
|
||||
@Component({
|
||||
selector: 'code-example',
|
||||
template: '<pre class="{{classes}}"><code class="{{animatedClasses}}" #codeContainer></code></pre>'
|
||||
})
|
||||
export class CodeExampleComponent implements OnInit, AfterViewInit {
|
||||
|
||||
@ViewChild('codeContainer') codeContainerRef: ElementRef;
|
||||
|
||||
language: string; // could be javascript, dart, typescript
|
||||
// TODO(i): escape doesn't seem to be currently supported in the original code
|
||||
escape: string; // could be 'html'
|
||||
format: string; // some css class
|
||||
showcase: string; // a string with the value 'true'
|
||||
animated = false;
|
||||
|
||||
// TODO(i): could we use @HostBinding instead or does the CSS have to be scoped to <pre> and <code>
|
||||
classes: string;
|
||||
animatedClasses: string;
|
||||
|
||||
|
||||
constructor(private elementRef: ElementRef) {
|
||||
// TODO(i): @Input should be supported for host elements and should just do a one off initialization of properties
|
||||
// from the host element => talk to Tobias
|
||||
['language', 'escape', 'format', 'showcase', 'animated'].forEach(inputName => {
|
||||
if (!this[inputName]) {
|
||||
this[inputName] = this.elementRef.nativeElement.getAttribute(inputName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
const showcaseClass = this.showcase === 'true' ? ' is-showcase' : '';
|
||||
this.classes = `
|
||||
prettyprint
|
||||
${this.format ? this.format : ''}
|
||||
${this.language ? 'lang-' + this.language : '' }
|
||||
${showcaseClass ? showcaseClass : ''}
|
||||
`.trim();
|
||||
|
||||
this.animatedClasses = `${this.animated ? 'animated fadeIn' : ''}`;
|
||||
|
||||
// Security: the codeExampleContent is the original innerHTML of the host element provided by
|
||||
// docs authors and as such its considered to be safe for innerHTML purposes
|
||||
this.codeContainerRef.nativeElement.innerHTML = this.elementRef.nativeElement.codeExampleContent;
|
||||
}
|
||||
|
||||
|
||||
ngAfterViewInit() {
|
||||
// TODO(i): import prettify.js from this file so that we don't need to preload it via index.html
|
||||
// whenever a code example is used, use syntax highlighting.
|
||||
// if(prettyPrint) {
|
||||
// prettyPrint();
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/* tslint:disable:no-unused-variable */
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Component, DebugElement, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
|
||||
|
||||
import { CodeExampleComponent } from './code-example.component';
|
||||
|
||||
describe('CodeExampleComponent', () => {
|
||||
let hostComponent: HostComponent;
|
||||
let codeComponent: TestCodeComponent;
|
||||
let codeExampleDe: DebugElement;
|
||||
let codeExampleComponent: CodeExampleComponent;
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
|
||||
const oneLineCode = `const foo = "bar";`;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CodeExampleComponent, HostComponent, TestCodeComponent ],
|
||||
});
|
||||
});
|
||||
|
||||
function createComponent(codeExampleContent = '') {
|
||||
fixture = TestBed.createComponent(HostComponent);
|
||||
hostComponent = fixture.componentInstance;
|
||||
codeExampleDe = fixture.debugElement.children[0];
|
||||
codeExampleComponent = codeExampleDe.componentInstance;
|
||||
codeComponent = codeExampleDe.query(By.directive(TestCodeComponent)).componentInstance;
|
||||
|
||||
// Copy the CodeExample's innerHTML (content)
|
||||
// into the `codeExampleContent` property as the DocViewer does
|
||||
codeExampleDe.nativeElement.codeExampleContent = codeExampleContent;
|
||||
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
it('should create CodeExampleComponent', () => {
|
||||
createComponent();
|
||||
expect(codeExampleComponent).toBeTruthy('CodeExampleComponent');
|
||||
});
|
||||
|
||||
it('should pass content to CodeComponent (<aio-code>)', () => {
|
||||
createComponent(oneLineCode);
|
||||
expect(codeComponent.code).toBe(oneLineCode);
|
||||
});
|
||||
|
||||
it('should pass language to CodeComponent', () => {
|
||||
TestBed.overrideComponent(HostComponent, {
|
||||
set: {template: '<code-example language="html"></code-example>'}});
|
||||
createComponent(oneLineCode);
|
||||
expect(codeComponent.language).toBe('html');
|
||||
});
|
||||
|
||||
it('should pass linenums to CodeComponent', () => {
|
||||
TestBed.overrideComponent(HostComponent, {
|
||||
set: {template: '<code-example linenums="true"></code-example>'}});
|
||||
createComponent(oneLineCode);
|
||||
expect(codeComponent.linenums).toBe('true');
|
||||
});
|
||||
|
||||
it('should add title (header) when set `title` attribute', () => {
|
||||
TestBed.overrideComponent(HostComponent, {
|
||||
set: {template: '<code-example title="Great Example"></code-example>'}});
|
||||
createComponent(oneLineCode);
|
||||
const actual = codeExampleDe.query(By.css('header')).nativeElement.innerText;
|
||||
expect(actual).toBe('Great Example');
|
||||
});
|
||||
});
|
||||
|
||||
//// Test helpers ////
|
||||
// tslint:disable:member-ordering
|
||||
@Component({
|
||||
selector: 'aio-code',
|
||||
template: `
|
||||
<div>lang: {{language}}</div>
|
||||
<div>linenums: {{linenums}}</div>
|
||||
code: <pre>{{someCode}}</pre>
|
||||
`
|
||||
})
|
||||
class TestCodeComponent {
|
||||
@Input() code = '';
|
||||
@Input() language: string;
|
||||
@Input() linenums: boolean | number;
|
||||
|
||||
get someCode() {
|
||||
return this.code && this.code.length > 30 ? this.code.substr(0, 30) + '...' : this.code;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'aio-host-comp',
|
||||
template: `<code-example></code-example>`
|
||||
})
|
||||
class HostComponent { }
|
|
@ -0,0 +1,41 @@
|
|||
/* tslint:disable component-selector */
|
||||
import { Component, ElementRef, OnInit } from '@angular/core';
|
||||
|
||||
/**
|
||||
* An embeddable code block that displays nicely formatted code.
|
||||
* Example usage:
|
||||
*
|
||||
* ```
|
||||
* <code-example language="ts" linenums="2" class="special" title="Do Stuff">
|
||||
* // a code block
|
||||
* console.log('do stuff');
|
||||
* </code-example>
|
||||
* ```
|
||||
*/
|
||||
@Component({
|
||||
selector: 'code-example',
|
||||
template: `
|
||||
<header *ngIf="title">{{title}}</header>
|
||||
<aio-code [code]="code" [language]="language" [linenums]="linenums"></aio-code>
|
||||
`
|
||||
})
|
||||
export class CodeExampleComponent implements OnInit { // implements AfterViewInit {
|
||||
|
||||
code: string;
|
||||
language: string;
|
||||
linenums: boolean | number;
|
||||
title: string;
|
||||
|
||||
constructor(private elementRef: ElementRef) {
|
||||
const element = this.elementRef.nativeElement;
|
||||
this.language = element.getAttribute('language') || '';
|
||||
this.linenums = element.getAttribute('linenums');
|
||||
this.title = element.getAttribute('title') || '';
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// The `codeExampleContent` property is set by the DocViewer when it builds this component.
|
||||
// It is the original innerHTML of the host element.
|
||||
this.code = this.elementRef.nativeElement.codeExampleContent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/* tslint:disable component-selector */
|
||||
import { Component, ElementRef, OnInit } from '@angular/core';
|
||||
|
||||
export interface TabInfo {
|
||||
title: string;
|
||||
language: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An embedded component used to generate tabbed code panes inside docs
|
||||
*
|
||||
* The innerHTML of the `<code-tabs>` component should contain `<code-pane>` elements.
|
||||
* Each `<code-pane>` has the same interface as the embedded `<code-example>` component.
|
||||
* The optional `linenums` attribute is the default `linenums` for each code pane.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'code-tabs',
|
||||
template: `
|
||||
<md-tab-group>
|
||||
<md-tab *ngFor="let tab of tabs">
|
||||
<template md-tab-label>
|
||||
<span class="{{tab.class}}">{{ tab.title }}</span>
|
||||
</template>
|
||||
<aio-code [code]="tab.code" [language]="tab.language" [linenums]="tab.linenums" class="{{ tab.class }}"></aio-code>
|
||||
</md-tab>
|
||||
</md-tab-group>
|
||||
`
|
||||
})
|
||||
export class CodeTabsComponent implements OnInit {
|
||||
tabs: TabInfo[];
|
||||
linenumsDefault: string;
|
||||
|
||||
constructor(private elementRef: ElementRef) { }
|
||||
|
||||
ngOnInit() {
|
||||
const element = this.elementRef.nativeElement;
|
||||
this.linenumsDefault = this.getLinenums(element);
|
||||
|
||||
// The `codeTabsContent` property is set by the DocViewer when it builds this component.
|
||||
// It is the original innerHTML of the host element.
|
||||
const content = element.codeTabsContent;
|
||||
this.processContent(content);
|
||||
}
|
||||
|
||||
processContent(content: string) {
|
||||
// We add it to an element so that we can easily parse the HTML
|
||||
const element = document.createElement('div');
|
||||
// **Security:** `codeTabsContent` is provided by docs authors and as such its considered to
|
||||
// be safe for innerHTML purposes.
|
||||
element.innerHTML = content;
|
||||
|
||||
this.tabs = [];
|
||||
const codeExamples = element.querySelectorAll('code-pane');
|
||||
for (let i = 0; i < codeExamples.length; i++) {
|
||||
const codeExample = codeExamples.item(i);
|
||||
const tab = {
|
||||
code: codeExample.innerHTML,
|
||||
class: codeExample.getAttribute('class'),
|
||||
language: codeExample.getAttribute('language'),
|
||||
linenums: this.getLinenums(codeExample),
|
||||
title: codeExample.getAttribute('title')
|
||||
};
|
||||
this.tabs.push(tab);
|
||||
}
|
||||
}
|
||||
|
||||
getLinenums(element: Element) {
|
||||
const linenums = element.getAttribute('linenums');
|
||||
return linenums == null ? this.linenumsDefault : linenums;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/* tslint:disable:no-unused-variable */
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
|
||||
import { CodeComponent } from './code.component';
|
||||
import { CopierService } from 'app/shared//copier.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
|
||||
const oneLineCode = 'const foo = "bar";';
|
||||
|
||||
const multiLineCode = `
|
||||
<hero-details>
|
||||
<h2>Bah Dah Bing</h2>
|
||||
<hero-team>
|
||||
<h3>NYC Team</h3>
|
||||
</hero-team>
|
||||
</hero-details>`;
|
||||
|
||||
describe('CodeComponent', () => {
|
||||
let codeComponentDe: DebugElement;
|
||||
let codeComponent: CodeComponent;
|
||||
let hostComponent: HostComponent;
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
|
||||
|
||||
// WARNING: Chance of cross-test pollution
|
||||
// CodeComponent injects PrettyPrintService
|
||||
// Once PrettyPrintService runs once _anywhere_, its ctor loads `prettify.js`
|
||||
// which sets `window['prettyPrintOne']`
|
||||
// That global survives these tests unless
|
||||
// we take strict measures to wipe it out in the `afterAll`
|
||||
// and make sure THAT runs after the tests by making component creation async
|
||||
afterAll(() => {
|
||||
delete window['prettyPrint'];
|
||||
delete window['prettyPrintOne'];
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CodeComponent, HostComponent ],
|
||||
providers: [
|
||||
PrettyPrinter,
|
||||
{provide: CopierService, useClass: TestCopierService },
|
||||
{provide: Logger, useClass: TestLogger }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
// Must be async because
|
||||
// CodeComponent creates PrettyPrintService which async loads `prettify.js`.
|
||||
// If not async, `afterAll` finishes before tests do!
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(HostComponent);
|
||||
hostComponent = fixture.componentInstance;
|
||||
codeComponentDe = fixture.debugElement.children[0];
|
||||
codeComponent = codeComponentDe.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should create CodeComponent', () => {
|
||||
expect(codeComponentDe.name).toBe('aio-code', 'selector');
|
||||
expect(codeComponent).toBeTruthy('CodeComponent');
|
||||
});
|
||||
|
||||
it('should format a one-line code sample', () => {
|
||||
// 'pln' spans are a tell-tale for syntax highlighing
|
||||
const spans = codeComponentDe.nativeElement.querySelectorAll('span.pln');
|
||||
expect(spans.length).toBeGreaterThan(0, 'formatted spans');
|
||||
});
|
||||
|
||||
it('should format a one-line code sample without linenums by default', () => {
|
||||
// `<li>`s are a tell-tale for line numbers
|
||||
const lis = codeComponentDe.nativeElement.querySelectorAll('li');
|
||||
expect(lis.length).toBe(0, 'should be no linenums');
|
||||
});
|
||||
|
||||
it('should add line numbers to one-line code sample when linenums set true', () => {
|
||||
hostComponent.linenums = 'true';
|
||||
fixture.detectChanges();
|
||||
|
||||
// `<li>`s are a tell-tale for line numbers
|
||||
const lis = codeComponentDe.nativeElement.querySelectorAll('li');
|
||||
expect(lis.length).toBe(1, 'has linenums');
|
||||
});
|
||||
|
||||
it('should format multi-line code with linenums by default', () => {
|
||||
hostComponent.code = multiLineCode;
|
||||
fixture.detectChanges();
|
||||
|
||||
// `<li>`s are a tell-tale for line numbers
|
||||
const lis = codeComponentDe.nativeElement.querySelectorAll('li');
|
||||
expect(lis.length).toBeGreaterThan(0, 'has linenums');
|
||||
});
|
||||
|
||||
it('should not format multi-line code when linenums set false', () => {
|
||||
hostComponent.linenums = false;
|
||||
hostComponent.code = multiLineCode;
|
||||
fixture.detectChanges();
|
||||
|
||||
// `<li>`s are a tell-tale for line numbers
|
||||
const lis = codeComponentDe.nativeElement.querySelectorAll('li');
|
||||
expect(lis.length).toBe(0, 'should be no linenums');
|
||||
});
|
||||
|
||||
it('should call copier service when copy button clicked', () => {
|
||||
const copierService: TestCopierService = <any> codeComponentDe.injector.get(CopierService) ;
|
||||
const button = fixture.debugElement.query(By.css('button')).nativeElement;
|
||||
expect(copierService.copyText.calls.count()).toBe(0, 'before click');
|
||||
button.click();
|
||||
expect(copierService.copyText.calls.count()).toBe(1, 'after click');
|
||||
});
|
||||
|
||||
it('should copy code text when copy button clicked', () => {
|
||||
const copierService: TestCopierService = <any> codeComponentDe.injector.get(CopierService) ;
|
||||
const button = fixture.debugElement.query(By.css('button')).nativeElement;
|
||||
button.click();
|
||||
expect(copierService.copyText.calls.argsFor(0)[0]).toEqual(oneLineCode, 'after click');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//// Test helpers ////
|
||||
// tslint:disable:member-ordering
|
||||
@Component({
|
||||
selector: 'aio-host-comp',
|
||||
template: `
|
||||
<aio-code [code]="code" [language]="language" [linenums]="linenums"></aio-code>
|
||||
`
|
||||
})
|
||||
class HostComponent {
|
||||
code = oneLineCode;
|
||||
language: string;
|
||||
linenums: boolean | number | string;
|
||||
}
|
||||
|
||||
class TestCopierService {
|
||||
copyText = jasmine.createSpy('copyText');
|
||||
}
|
||||
|
||||
class TestLogger {
|
||||
log = jasmine.createSpy('log');
|
||||
error = jasmine.createSpy('error');
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
import { Component, ElementRef, ViewChild, OnChanges, OnDestroy, Input } from '@angular/core';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
import { CopierService } from 'app/shared/copier.service';
|
||||
|
||||
const originalLabel = 'Copy Code';
|
||||
const copiedLabel = 'Copied!';
|
||||
|
||||
/**
|
||||
* Formatted Code Block
|
||||
*
|
||||
* Pretty renders a code block, used in the docs and API reference by the code-example and
|
||||
* code-tabs embedded components.
|
||||
* It includes a "copy" button that will send the content to the clipboard when clicked
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* ```
|
||||
* <aio-code [code]="variableContainingCode" [language]="ts" [linenums]="true"></aio-code>
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@Component({
|
||||
selector: 'aio-code',
|
||||
template: `
|
||||
<button class="copy-button" #copyButton (click)="doCopy()">{{ buttonLabel }}</button>
|
||||
<pre class="prettyprint lang-{{language}}">
|
||||
<code class="animated fadeIn" #codeContainer></code>
|
||||
</pre>
|
||||
`
|
||||
})
|
||||
export class CodeComponent implements OnChanges {
|
||||
|
||||
/**
|
||||
* The language of the code to render
|
||||
* (could be javascript, dart, typescript, etc)
|
||||
*/
|
||||
@Input()
|
||||
language: string;
|
||||
|
||||
/**
|
||||
* Whether to display line numbers:
|
||||
* - false: don't display
|
||||
* - true: do display
|
||||
* - number: do display but start at the given number
|
||||
*/
|
||||
@Input()
|
||||
linenums: boolean | number | string;
|
||||
|
||||
/**
|
||||
* The code to be formatted, this should already be HTML encoded
|
||||
*/
|
||||
@Input()
|
||||
code: string;
|
||||
|
||||
/**
|
||||
* The label to show on the copy button
|
||||
*/
|
||||
buttonLabel = originalLabel;
|
||||
|
||||
/**
|
||||
* The element in the template that will display the formatted code
|
||||
*/
|
||||
@ViewChild('codeContainer') codeContainer: ElementRef;
|
||||
|
||||
constructor(
|
||||
private pretty: PrettyPrinter,
|
||||
private copier: CopierService,
|
||||
private logger: Logger) {}
|
||||
|
||||
ngOnChanges() {
|
||||
if (!this.code) { return; }
|
||||
|
||||
const linenums = this.getLinenums();
|
||||
|
||||
this.setCodeHtml(this.code); // start with unformatted code
|
||||
this.pretty.formatCode(this.code, this.language, linenums).subscribe(
|
||||
formattedCode => this.setCodeHtml(formattedCode),
|
||||
err => { /* ignore failure to format */ }
|
||||
);
|
||||
}
|
||||
|
||||
private setCodeHtml(formattedCode: string) {
|
||||
// **Security:** `codeExampleContent` is provided by docs authors and as such its considered to
|
||||
// be safe for innerHTML purposes.
|
||||
this.codeContainer.nativeElement.innerHTML = formattedCode;
|
||||
}
|
||||
|
||||
doCopy() {
|
||||
// We take the innerText because we don't want it to be HTML encoded
|
||||
const code = this.codeContainer.nativeElement.innerText;
|
||||
if (this.copier.copyText(code)) {
|
||||
this.logger.log('Copied code to clipboard:', code);
|
||||
// change the button label (for one second)
|
||||
this.buttonLabel = copiedLabel;
|
||||
setTimeout(() => this.buttonLabel = originalLabel, 1000);
|
||||
} else {
|
||||
this.logger.error('ERROR copying code to clipboard:', code);
|
||||
}
|
||||
}
|
||||
|
||||
getLinenums() {
|
||||
const linenums =
|
||||
typeof this.linenums === 'boolean' ? this.linenums :
|
||||
this.linenums === 'true' ? true :
|
||||
this.linenums === 'false' ? false :
|
||||
typeof this.linenums === 'string' ? parseInt(this.linenums, 10) :
|
||||
this.linenums;
|
||||
|
||||
// if no linenums, enable line numbers if more than one line
|
||||
return linenums == null || linenums === NaN ?
|
||||
(this.code.match(/\n/g) || []).length > 1 : linenums;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { fromPromise } from 'rxjs/observable/fromPromise';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/first';
|
||||
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
|
||||
declare const System;
|
||||
|
||||
type PrettyPrintOne = (code: string, language?: string, linenums?: number | boolean) => string;
|
||||
|
||||
/**
|
||||
* Wrapper around the prettify.js library
|
||||
*/
|
||||
@Injectable()
|
||||
export class PrettyPrinter {
|
||||
|
||||
private prettyPrintOne: Observable<PrettyPrintOne>;
|
||||
|
||||
constructor(private logger: Logger) {
|
||||
this.prettyPrintOne = fromPromise(this.getPrettyPrintOne()).share();
|
||||
}
|
||||
|
||||
private getPrettyPrintOne(): Promise<PrettyPrintOne> {
|
||||
const ppo = window['prettyPrintOne'];
|
||||
return ppo ? Promise.resolve(ppo) :
|
||||
// prettify.js is not in window global; load it with webpack loader
|
||||
System.import('assets/js/prettify.js')
|
||||
.then(
|
||||
() => window['prettyPrintOne'],
|
||||
err => {
|
||||
const msg = 'Cannot get prettify.js from server';
|
||||
this.logger.error(msg, err);
|
||||
// return a pretty print fn that always fails.
|
||||
return () => { throw new Error(msg); };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format code snippet as HTML
|
||||
* @param {string} code - the code snippet to format; should already be HTML encoded
|
||||
* @param {string} [language] - The language of the code to render (could be javascript, html, typescript, etc)
|
||||
* @param {string|number} [linenums] - Whether to display line numbers:
|
||||
* - false: don't display
|
||||
* - true: do display
|
||||
* - number: do display but start at the given number
|
||||
* @returns Observable<string> - Observable of formatted code
|
||||
*/
|
||||
formatCode(code: string, language?: string, linenums?: number | boolean) {
|
||||
return this.prettyPrintOne.map(ppo => {
|
||||
try {
|
||||
return ppo(code, language, linenums);
|
||||
} catch (err) {
|
||||
const msg = `Could not format code that begins '${code.substr(0, 50)}...'.`;
|
||||
console.error(msg, err);
|
||||
throw new Error(msg);
|
||||
}
|
||||
})
|
||||
.first(); // complete immediately
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { PrettyPrinter } from './code/pretty-printer.service';
|
||||
import { CopierService } from 'app/shared/copier.service';
|
||||
|
||||
|
||||
// Any components that we want to use inside embedded components must be declared or imported here
|
||||
// It is not enough just to import them inside the AppModule
|
||||
|
||||
// Reusable components (used inside embedded components)
|
||||
import { MdTabsModule } from '@angular/material';
|
||||
import { CodeComponent } from './code/code.component';
|
||||
|
||||
// Embedded Components
|
||||
import { ApiListComponent } from './api/api-list.component';
|
||||
import { CodeExampleComponent } from './code/code-example.component';
|
||||
import { CodeTabsComponent } from './code/code-tabs.component';
|
||||
import { DocTitleComponent } from './doc-title.component';
|
||||
|
||||
/** Components that can be embedded in docs
|
||||
* such as CodeExampleComponent, LiveExampleComponent,...
|
||||
*/
|
||||
export const embeddedComponents: any[] = [
|
||||
ApiListComponent, CodeExampleComponent, DocTitleComponent, CodeTabsComponent
|
||||
];
|
||||
|
||||
/** Injectable class w/ property returning components that can be embedded in docs */
|
||||
export class EmbeddedComponents {
|
||||
components = embeddedComponents;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, MdTabsModule ],
|
||||
declarations: [
|
||||
embeddedComponents,
|
||||
CodeComponent
|
||||
],
|
||||
providers: [
|
||||
EmbeddedComponents,
|
||||
PrettyPrinter,
|
||||
CopierService
|
||||
],
|
||||
entryComponents: [ embeddedComponents ]
|
||||
})
|
||||
export class EmbeddedModule { }
|
|
@ -1,13 +0,0 @@
|
|||
import { ApiListComponent } from './api/api-list.component';
|
||||
import { CodeExampleComponent } from './code-example.component';
|
||||
import { DocTitleComponent } from './doc-title.component';
|
||||
|
||||
/** Components that can be embedded in docs such as CodeExampleComponent, LiveExampleComponent,... */
|
||||
export const embeddedComponents: any[] = [
|
||||
ApiListComponent, CodeExampleComponent, DocTitleComponent
|
||||
];
|
||||
|
||||
/** Injectable class w/ property returning components that can be embedded in docs */
|
||||
export class EmbeddedComponents {
|
||||
components = embeddedComponents;
|
||||
}
|
|
@ -3,7 +3,7 @@ import { ComponentFactoryResolver, ElementRef, Injector, NgModule, OnInit, ViewC
|
|||
import { By } from '@angular/platform-browser';
|
||||
import { DocViewerComponent } from './doc-viewer.component';
|
||||
import { DocumentContents } from 'app/documents/document.service';
|
||||
import { embeddedComponents, EmbeddedComponents } from 'app/embedded';
|
||||
import { EmbeddedModule, embeddedComponents, EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
|
||||
|
||||
/// Embedded Test Components ///
|
||||
|
@ -65,9 +65,10 @@ class BazComponent implements OnInit {
|
|||
}
|
||||
///// Test Module //////
|
||||
|
||||
const embeddedTestComponents = [FooComponent, BarComponent, BazComponent, ...embeddedComponents];
|
||||
const embeddedTestComponents = [FooComponent, BarComponent, BazComponent];
|
||||
|
||||
@NgModule({
|
||||
imports: [ EmbeddedModule ],
|
||||
entryComponents: embeddedTestComponents
|
||||
})
|
||||
class TestModule { }
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
Output, ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
|
||||
import { EmbeddedComponents } from 'app/embedded';
|
||||
import { EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
import { DocumentContents } from 'app/documents/document.service';
|
||||
|
||||
interface EmbeddedComponentFactory {
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* This class is based on the code in the following projects:
|
||||
*
|
||||
* - https://github.com/zenorocha/select
|
||||
* - https://github.com/zenorocha/clipboard.js/
|
||||
*
|
||||
* Both released under MIT license - © Zeno Rocha
|
||||
*/
|
||||
|
||||
|
||||
export class CopierService {
|
||||
private fakeElem: HTMLTextAreaElement;
|
||||
|
||||
/**
|
||||
* Creates a fake textarea element, sets its value from `text` property,
|
||||
* and makes a selection on it.
|
||||
*/
|
||||
createFake(text: string) {
|
||||
const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
|
||||
|
||||
// Create a fake element to hold the contents to copy
|
||||
this.fakeElem = document.createElement('textarea');
|
||||
|
||||
// Prevent zooming on iOS
|
||||
this.fakeElem.style.fontSize = '12pt';
|
||||
|
||||
// Reset box model
|
||||
this.fakeElem.style.border = '0';
|
||||
this.fakeElem.style.padding = '0';
|
||||
this.fakeElem.style.margin = '0';
|
||||
|
||||
// Move element out of screen horizontally
|
||||
this.fakeElem.style.position = 'absolute';
|
||||
this.fakeElem.style[ isRTL ? 'right' : 'left' ] = '-9999px';
|
||||
|
||||
// Move element to the same position vertically
|
||||
const yPosition = window.pageYOffset || document.documentElement.scrollTop;
|
||||
this.fakeElem.style.top = yPosition + 'px';
|
||||
|
||||
this.fakeElem.setAttribute('readonly', '');
|
||||
this.fakeElem.value = text;
|
||||
|
||||
document.body.appendChild(this.fakeElem);
|
||||
|
||||
this.fakeElem.select();
|
||||
this.fakeElem.setSelectionRange(0, this.fakeElem.value.length);
|
||||
}
|
||||
|
||||
removeFake() {
|
||||
if (this.fakeElem) {
|
||||
document.body.removeChild(this.fakeElem);
|
||||
this.fakeElem = null;
|
||||
}
|
||||
}
|
||||
|
||||
copyText(text: string) {
|
||||
try {
|
||||
this.createFake(text);
|
||||
return document.execCommand('copy');
|
||||
} catch (err) {
|
||||
return false;
|
||||
} finally {
|
||||
this.removeFake();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
!function(){/*
|
||||
|
||||
Copyright (C) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
window.PR_SHOULD_USE_CONTINUATION=!0;
|
||||
(function(){function T(a){function d(e){var b=e.charCodeAt(0);if(92!==b)return b;var a=e.charAt(1);return(b=w[a])?b:"0"<=a&&"7">=a?parseInt(e.substring(1),8):"u"===a||"x"===a?parseInt(e.substring(2),16):e.charCodeAt(1)}function f(e){if(32>e)return(16>e?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return"\\"===e||"-"===e||"]"===e||"^"===e?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[0-9A-Fa-f]{4}|\\x[0-9A-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\s\S]|-|[^-\\]/g);e=
|
||||
[];var a="^"===b[0],c=["["];a&&c.push("^");for(var a=a?1:0,g=b.length;a<g;++a){var h=b[a];if(/\\[bdsw]/i.test(h))c.push(h);else{var h=d(h),k;a+2<g&&"-"===b[a+1]?(k=d(b[a+2]),a+=2):k=h;e.push([h,k]);65>k||122<h||(65>k||90<h||e.push([Math.max(65,h)|32,Math.min(k,90)|32]),97>k||122<h||e.push([Math.max(97,h)&-33,Math.min(k,122)&-33]))}}e.sort(function(e,a){return e[0]-a[0]||a[1]-e[1]});b=[];g=[];for(a=0;a<e.length;++a)h=e[a],h[0]<=g[1]+1?g[1]=Math.max(g[1],h[1]):b.push(g=h);for(a=0;a<b.length;++a)h=b[a],
|
||||
c.push(f(h[0])),h[1]>h[0]&&(h[1]+1>h[0]&&c.push("-"),c.push(f(h[1])));c.push("]");return c.join("")}function v(e){for(var a=e.source.match(/(?:\[(?:[^\x5C\x5D]|\\[\s\S])*\]|\\u[A-Fa-f0-9]{4}|\\x[A-Fa-f0-9]{2}|\\[0-9]+|\\[^ux0-9]|\(\?[:!=]|[\(\)\^]|[^\x5B\x5C\(\)\^]+)/g),c=a.length,d=[],g=0,h=0;g<c;++g){var k=a[g];"("===k?++h:"\\"===k.charAt(0)&&(k=+k.substring(1))&&(k<=h?d[k]=-1:a[g]=f(k))}for(g=1;g<d.length;++g)-1===d[g]&&(d[g]=++A);for(h=g=0;g<c;++g)k=a[g],"("===k?(++h,d[h]||(a[g]="(?:")):"\\"===
|
||||
k.charAt(0)&&(k=+k.substring(1))&&k<=h&&(a[g]="\\"+d[k]);for(g=0;g<c;++g)"^"===a[g]&&"^"!==a[g+1]&&(a[g]="");if(e.ignoreCase&&n)for(g=0;g<c;++g)k=a[g],e=k.charAt(0),2<=k.length&&"["===e?a[g]=b(k):"\\"!==e&&(a[g]=k.replace(/[a-zA-Z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var A=0,n=!1,l=!1,m=0,c=a.length;m<c;++m){var p=a[m];if(p.ignoreCase)l=!0;else if(/[a-z]/i.test(p.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,""))){n=!0;
|
||||
l=!1;break}}for(var w={b:8,t:9,n:10,v:11,f:12,r:13},r=[],m=0,c=a.length;m<c;++m){p=a[m];if(p.global||p.multiline)throw Error(""+p);r.push("(?:"+v(p)+")")}return new RegExp(r.join("|"),l?"gi":"g")}function U(a,d){function f(a){var c=a.nodeType;if(1==c){if(!b.test(a.className)){for(c=a.firstChild;c;c=c.nextSibling)f(c);c=a.nodeName.toLowerCase();if("br"===c||"li"===c)v[l]="\n",n[l<<1]=A++,n[l++<<1|1]=a}}else if(3==c||4==c)c=a.nodeValue,c.length&&(c=d?c.replace(/\r\n?/g,"\n"):c.replace(/[ \t\r\n]+/g,
|
||||
" "),v[l]=c,n[l<<1]=A,A+=c.length,n[l++<<1|1]=a)}var b=/(?:^|\s)nocode(?:\s|$)/,v=[],A=0,n=[],l=0;f(a);return{a:v.join("").replace(/\n$/,""),c:n}}function J(a,d,f,b,v){f&&(a={h:a,l:1,j:null,m:null,a:f,c:null,i:d,g:null},b(a),v.push.apply(v,a.g))}function V(a){for(var d=void 0,f=a.firstChild;f;f=f.nextSibling)var b=f.nodeType,d=1===b?d?a:f:3===b?W.test(f.nodeValue)?a:d:d;return d===a?void 0:d}function G(a,d){function f(a){for(var l=a.i,m=a.h,c=[l,"pln"],p=0,w=a.a.match(v)||[],r={},e=0,t=w.length;e<
|
||||
t;++e){var z=w[e],q=r[z],g=void 0,h;if("string"===typeof q)h=!1;else{var k=b[z.charAt(0)];if(k)g=z.match(k[1]),q=k[0];else{for(h=0;h<A;++h)if(k=d[h],g=z.match(k[1])){q=k[0];break}g||(q="pln")}!(h=5<=q.length&&"lang-"===q.substring(0,5))||g&&"string"===typeof g[1]||(h=!1,q="src");h||(r[z]=q)}k=p;p+=z.length;if(h){h=g[1];var B=z.indexOf(h),D=B+h.length;g[2]&&(D=z.length-g[2].length,B=D-h.length);q=q.substring(5);J(m,l+k,z.substring(0,B),f,c);J(m,l+k+B,h,K(q,h),c);J(m,l+k+D,z.substring(D),f,c)}else c.push(l+
|
||||
k,q)}a.g=c}var b={},v;(function(){for(var f=a.concat(d),l=[],m={},c=0,p=f.length;c<p;++c){var w=f[c],r=w[3];if(r)for(var e=r.length;0<=--e;)b[r.charAt(e)]=w;w=w[1];r=""+w;m.hasOwnProperty(r)||(l.push(w),m[r]=null)}l.push(/[\0-\uffff]/);v=T(l)})();var A=d.length;return f}function y(a){var d=[],f=[];a.tripleQuotedStrings?d.push(["str",/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
|
||||
null,"'\""]):a.multiLineStrings?d.push(["str",/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"]):d.push(["str",/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"]);a.verbatimStrings&&f.push(["str",/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null]);var b=a.hashComments;b&&(a.cStyleComments?(1<b?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\r\n]*)/,
|
||||
null,"#"]),f.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,null])):d.push(["com",/^#[^\r\n]*/,null,"#"]));a.cStyleComments&&(f.push(["com",/^\/\/[^\r\n]*/,null]),f.push(["com",/^\/\*[\s\S]*?(?:\*\/|$)/,null]));if(b=a.regexLiterals){var v=(b=1<b?"":"\n\r")?".":"[\\S\\s]";f.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+
|
||||
("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+v+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+v+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&f.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&f.push(["kwd",new RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),null]);d.push(["pln",/^\s+/,null," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");f.push(["lit",/^@[a-z_$][a-z_$@0-9]*/i,null],["typ",/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],["pln",/^[a-z_$][a-z_$@0-9]*/i,
|
||||
null],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],["pln",/^\\[\s\S]?/,null],["pun",new RegExp(b),null]);return G(d,f)}function L(a,d,f){function b(a){var c=a.nodeType;if(1==c&&!A.test(a.className))if("br"===a.nodeName)v(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((3==c||4==c)&&f){var d=a.nodeValue,q=d.match(n);q&&(c=d.substring(0,q.index),a.nodeValue=c,(d=d.substring(q.index+q[0].length))&&
|
||||
a.parentNode.insertBefore(l.createTextNode(d),a.nextSibling),v(a),c||a.parentNode.removeChild(a))}}function v(a){function b(a,c){var d=c?a.cloneNode(!1):a,k=a.parentNode;if(k){var k=b(k,1),e=a.nextSibling;k.appendChild(d);for(var f=e;f;f=e)e=f.nextSibling,k.appendChild(f)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;a=b(a.nextSibling,0);for(var d;(d=a.parentNode)&&1===d.nodeType;)a=d;c.push(a)}for(var A=/(?:^|\s)nocode(?:\s|$)/,n=/\r\n?|\n/,l=a.ownerDocument,m=l.createElement("li");a.firstChild;)m.appendChild(a.firstChild);
|
||||
for(var c=[m],p=0;p<c.length;++p)b(c[p]);d===(d|0)&&c[0].setAttribute("value",d);var w=l.createElement("ol");w.className="linenums";d=Math.max(0,d-1|0)||0;for(var p=0,r=c.length;p<r;++p)m=c[p],m.className="L"+(p+d)%10,m.firstChild||m.appendChild(l.createTextNode("\u00a0")),w.appendChild(m);a.appendChild(w)}function t(a,d){for(var f=d.length;0<=--f;){var b=d[f];I.hasOwnProperty(b)?E.console&&console.warn("cannot override language handler %s",b):I[b]=a}}function K(a,d){a&&I.hasOwnProperty(a)||(a=/^\s*</.test(d)?
|
||||
"default-markup":"default-code");return I[a]}function M(a){var d=a.j;try{var f=U(a.h,a.l),b=f.a;a.a=b;a.c=f.c;a.i=0;K(d,b)(a);var v=/\bMSIE\s(\d+)/.exec(navigator.userAgent),v=v&&8>=+v[1],d=/\n/g,A=a.a,n=A.length,f=0,l=a.c,m=l.length,b=0,c=a.g,p=c.length,w=0;c[p]=n;var r,e;for(e=r=0;e<p;)c[e]!==c[e+2]?(c[r++]=c[e++],c[r++]=c[e++]):e+=2;p=r;for(e=r=0;e<p;){for(var t=c[e],z=c[e+1],q=e+2;q+2<=p&&c[q+1]===z;)q+=2;c[r++]=t;c[r++]=z;e=q}c.length=r;var g=a.h;a="";g&&(a=g.style.display,g.style.display="none");
|
||||
try{for(;b<m;){var h=l[b+2]||n,k=c[w+2]||n,q=Math.min(h,k),B=l[b+1],D;if(1!==B.nodeType&&(D=A.substring(f,q))){v&&(D=D.replace(d,"\r"));B.nodeValue=D;var N=B.ownerDocument,u=N.createElement("span");u.className=c[w+1];var y=B.parentNode;y.replaceChild(u,B);u.appendChild(B);f<h&&(l[b+1]=B=N.createTextNode(A.substring(q,h)),y.insertBefore(B,u.nextSibling))}f=q;f>=h&&(b+=2);f>=k&&(w+=2)}}finally{g&&(g.style.display=a)}}catch(x){E.console&&console.log(x&&x.stack||x)}}var E=window,C=["break,continue,do,else,for,if,return,while"],
|
||||
F=[[C,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,restrict,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],H=[F,"alignas,alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,noexcept,noreturn,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],
|
||||
O=[F,"abstract,assert,boolean,byte,extends,finally,final,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],P=[F,"abstract,add,alias,as,ascending,async,await,base,bool,by,byte,checked,decimal,delegate,descending,dynamic,event,finally,fixed,foreach,from,get,global,group,implicit,in,interface,internal,into,is,join,let,lock,null,object,out,override,orderby,params,partial,readonly,ref,remove,sbyte,sealed,select,set,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,value,var,virtual,where,yield"],
|
||||
F=[F,"abstract,async,await,constructor,debugger,enum,eval,export,function,get,implements,instanceof,interface,let,null,set,undefined,var,with,yield,Infinity,NaN"],Q=[C,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],R=[C,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],C=[C,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],
|
||||
S=/^(DIR|FILE|array|vector|(de|priority_)?queue|(forward_)?list|stack|(const_)?(reverse_)?iterator|(unordered_)?(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,W=/\S/,X=y({keywords:[H,P,O,F,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",Q,R,C],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),I={};t(X,["default-code"]);t(G([],[["pln",/^[^<?]+/],["dec",
|
||||
/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),"default-markup htm html mxml xhtml xml xsl".split(" "));t(G([["pln",/^[\s]+/,null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,
|
||||
"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],["pun",/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);t(G([],[["atv",/^[\s\S]+/]]),["uq.val"]);t(y({keywords:H,
|
||||
hashComments:!0,cStyleComments:!0,types:S}),"c cc cpp cxx cyc m".split(" "));t(y({keywords:"null,true,false"}),["json"]);t(y({keywords:P,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:S}),["cs"]);t(y({keywords:O,cStyleComments:!0}),["java"]);t(y({keywords:C,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);t(y({keywords:Q,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);t(y({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",
|
||||
hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);t(y({keywords:R,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);t(y({keywords:F,cStyleComments:!0,regexLiterals:!0}),["javascript","js","ts","typescript"]);t(y({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,
|
||||
regexLiterals:!0}),["coffee"]);t(G([],[["str",/^[\s\S]+/]]),["regex"]);var Y=E.PR={createSimpleLexer:G,registerLangHandler:t,sourceDecorator:y,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:E.prettyPrintOne=function(a,d,f){f=f||!1;d=d||null;var b=document.createElement("div");b.innerHTML="<pre>"+a+"</pre>";
|
||||
b=b.firstChild;f&&L(b,f,!0);M({j:d,m:f,h:b,l:1,a:null,i:null,c:null,g:null});return b.innerHTML},prettyPrint:E.prettyPrint=function(a,d){function f(){for(var b=E.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;p<t.length&&c.now()<b;p++){for(var d=t[p],l=g,m=d;m=m.previousSibling;){var n=m.nodeType,u=(7===n||8===n)&&m.nodeValue;if(u?!/^\??prettify\b/.test(u):3!==n||/\S/.test(m.nodeValue))break;if(u){l={};u.replace(/\b(\w+)=([\w:.%+-]+)/g,function(a,b,c){l[b]=c});break}}m=d.className;if((l!==g||r.test(m))&&
|
||||
!e.test(m)){n=!1;for(u=d.parentNode;u;u=u.parentNode)if(q.test(u.tagName)&&u.className&&r.test(u.className)){n=!0;break}if(!n){d.className+=" prettyprinted";n=l.lang;if(!n){var n=m.match(w),C;!n&&(C=V(d))&&z.test(C.tagName)&&(n=C.className.match(w));n&&(n=n[1])}if(y.test(d.tagName))u=1;else var u=d.currentStyle,x=v.defaultView,u=(u=u?u.whiteSpace:x&&x.getComputedStyle?x.getComputedStyle(d,null).getPropertyValue("white-space"):0)&&"pre"===u.substring(0,3);x=l.linenums;(x="true"===x||+x)||(x=(x=m.match(/\blinenums\b(?::(\d+))?/))?
|
||||
x[1]&&x[1].length?+x[1]:!0:!1);x&&L(d,x,u);M({j:n,h:d,m:x,l:u,a:null,i:null,c:null,g:null})}}}p<t.length?E.setTimeout(f,250):"function"===typeof a&&a()}for(var b=d||document.body,v=b.ownerDocument||document,b=[b.getElementsByTagName("pre"),b.getElementsByTagName("code"),b.getElementsByTagName("xmp")],t=[],n=0;n<b.length;++n)for(var l=0,m=b[n].length;l<m;++l)t.push(b[n][l]);var b=null,c=Date;c.now||(c={now:function(){return+new Date}});var p=0,w=/\blang(?:uage)?-([\w.]+)(?!\S)/,r=/\bprettyprint\b/,
|
||||
e=/\bprettyprinted\b/,y=/pre|xmp/i,z=/^code$/i,q=/^(?:pre|code|xmp)$/i,g={};f()}},H=E.define;"function"===typeof H&&H.amd&&H("google-code-prettify",[],function(){return Y})})();}()
|
|
@ -1,24 +1,180 @@
|
|||
code-example code {
|
||||
@include codeblock($backgroundgray);
|
||||
code-example,
|
||||
code-tabs md-tab-body {
|
||||
background-color: $backgroundgray;
|
||||
border: 0.5px solid $lightgray;
|
||||
border-radius: 5px;
|
||||
color: $darkgray;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
code-example[language=bash] code {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.prettyprint {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
code-example header {
|
||||
background-color: $mediumgray;
|
||||
border: 0.5px solid $mediumgray;
|
||||
border-radius: 5px 5px 0 0;
|
||||
color: $offwhite;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
margin: -10px;
|
||||
}
|
||||
|
||||
code-example.is-anti-pattern header {
|
||||
border: 2px solid $anti-pattern;
|
||||
background: $anti-pattern;
|
||||
}
|
||||
|
||||
code-example.is-anti-pattern,
|
||||
code-tabs.is-anti-pattern md-tab-body {
|
||||
border: 0.5px solid $anti-pattern;
|
||||
}
|
||||
|
||||
aio-code pre {
|
||||
display: flex;
|
||||
}
|
||||
line-height: 1.5;
|
||||
|
||||
.prettyprint.lang-bash code {
|
||||
@include codeblock($darkgray);
|
||||
color: $codegreen;
|
||||
}
|
||||
|
||||
.code-example pre, code-example pre {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
white-space: pre-wrap;
|
||||
li {
|
||||
line-height: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
$blue-grey-50: #ECEFF1;
|
||||
$blue-grey-100: #CFD8DC;
|
||||
$blue-grey-200: #B0BEC5;
|
||||
$blue-grey-300: #90A4AE;
|
||||
$blue-grey-400: #78909C;
|
||||
$blue-grey-500: #607D8B;
|
||||
$blue-grey-600: #546E7A;
|
||||
$blue-grey-700: #455A64;
|
||||
$blue-grey-800: #37474F;
|
||||
$blue-grey-900: #263238;
|
||||
|
||||
$pink-50: #FCE4EC;
|
||||
$pink-100: #F8BBD0;
|
||||
$pink-200: #F48FB1;
|
||||
$pink-300: #F06292;
|
||||
$pink-400: #EC407A;
|
||||
$pink-500: #E91E63;
|
||||
$pink-600: #D81B60;
|
||||
$pink-700: #C2185B;
|
||||
$pink-800: #AD1457;
|
||||
$pink-900: #880E4F;
|
||||
$pink-A100: #FF80AB;
|
||||
$pink-A200: #FF4081;
|
||||
$pink-A400: #F50057;
|
||||
$pink-A700: #C51162;
|
||||
|
||||
$teal-50: #E0F2F1;
|
||||
$teal-100: #B2DFDB;
|
||||
$teal-200: #80CBC4;
|
||||
$teal-300: #4DB6AC;
|
||||
$teal-400: #26A69A;
|
||||
$teal-500: #009688;
|
||||
$teal-600: #00897B;
|
||||
$teal-700: #00796B;
|
||||
$teal-800: #00695C;
|
||||
$teal-900: #004D40;
|
||||
$teal-A100: #A7FFEB;
|
||||
$teal-A200: #64FFDA;
|
||||
$teal-A400: #1DE9B6;
|
||||
$teal-A700: #00BFA5;
|
||||
|
||||
$white: #FFFFFF;
|
||||
|
||||
/*
|
||||
* Screen Colors
|
||||
*
|
||||
*/
|
||||
|
||||
.pnk,
|
||||
.blk {
|
||||
border-radius: 4px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
.pnk {
|
||||
background: $blue-grey-50;
|
||||
color: $blue-grey-900;
|
||||
}
|
||||
.blk {
|
||||
background: $blue-grey-900;
|
||||
}
|
||||
.otl {
|
||||
outline: 1px solid rgba($blue-grey-700, .56);
|
||||
}
|
||||
.kwd {
|
||||
color: $pink-600;
|
||||
}
|
||||
.typ,
|
||||
.tag {
|
||||
color: $pink-600;
|
||||
}
|
||||
.str,
|
||||
.atv {
|
||||
color: $teal-700;
|
||||
}
|
||||
.atn {
|
||||
color: $teal-700;
|
||||
}
|
||||
.com {
|
||||
color: $teal-700;
|
||||
}
|
||||
.lit {
|
||||
color: $teal-700;
|
||||
}
|
||||
.pun {
|
||||
color: $blue-grey-700;
|
||||
}
|
||||
.pln {
|
||||
color: $blue-grey-700;
|
||||
}
|
||||
.dec {
|
||||
color: $teal-700;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Print Colors
|
||||
*
|
||||
*/
|
||||
|
||||
@media print {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
|
||||
ol {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.kwd {
|
||||
color: $pink-600;
|
||||
}
|
||||
.typ,
|
||||
.tag {
|
||||
color: $pink-600;
|
||||
}
|
||||
.str,
|
||||
.atv {
|
||||
color: $teal-700;
|
||||
}
|
||||
.atn {
|
||||
color: $teal-700;
|
||||
}
|
||||
.com {
|
||||
color: $teal-700;
|
||||
}
|
||||
.lit {
|
||||
color: $teal-700;
|
||||
}
|
||||
.pun {
|
||||
color: $blue-grey-700;
|
||||
}
|
||||
.pln {
|
||||
color: $blue-grey-700;
|
||||
}
|
||||
.dec {
|
||||
color: $teal-700;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ $darkgray: #333;
|
|||
$black: #0A1014;
|
||||
$codegreen: #17ff0b;
|
||||
$orange: #FF9800;
|
||||
$anti-pattern: $brightred;
|
||||
|
||||
// GRADIENTS
|
||||
$bluegradient: linear-gradient(145deg,#0D47A1,#42A5F5);
|
||||
|
|
Loading…
Reference in New Issue