2017-03-26 13:32:29 -07:00
|
|
|
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';
|
2017-04-24 21:19:40 +01:00
|
|
|
import { MdSnackBar } from '@angular/material';
|
2017-03-26 13:32:29 -07:00
|
|
|
|
|
|
|
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: `
|
2017-04-01 21:16:22 +01:00
|
|
|
|
2017-03-26 13:32:29 -07:00
|
|
|
<pre class="prettyprint lang-{{language}}">
|
2017-04-01 17:57:47 -07:00
|
|
|
<button *ngIf="code" class="material-icons copy-button" (click)="doCopy()">content_copy</button>
|
2017-03-26 13:32:29 -07:00
|
|
|
<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 element in the template that will display the formatted code
|
|
|
|
*/
|
|
|
|
@ViewChild('codeContainer') codeContainer: ElementRef;
|
|
|
|
|
|
|
|
constructor(
|
2017-04-24 21:19:40 +01:00
|
|
|
private snackbar: MdSnackBar,
|
2017-03-26 13:32:29 -07:00
|
|
|
private pretty: PrettyPrinter,
|
|
|
|
private copier: CopierService,
|
|
|
|
private logger: Logger) {}
|
|
|
|
|
|
|
|
ngOnChanges() {
|
2017-04-10 22:14:40 +01:00
|
|
|
this.code = this.code && leftAlign(this.code);
|
2017-03-26 13:32:29 -07:00
|
|
|
|
2017-04-01 17:57:47 -07:00
|
|
|
if (!this.code) {
|
|
|
|
this.setCodeHtml('<p class="code-missing">The code sample is missing.</p>');
|
|
|
|
return;
|
|
|
|
}
|
2017-04-01 21:16:22 +01:00
|
|
|
|
2017-03-26 13:32:29 -07:00
|
|
|
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);
|
2017-04-24 21:19:40 +01:00
|
|
|
// success snackbar alert
|
|
|
|
this.snackbar.open('Code Copied', '', {
|
|
|
|
duration: 800,
|
|
|
|
});
|
2017-03-26 13:32:29 -07:00
|
|
|
} else {
|
|
|
|
this.logger.error('ERROR copying code to clipboard:', code);
|
2017-04-24 21:19:40 +01:00
|
|
|
// failure snackbar alert
|
|
|
|
this.snackbar.open('Copy failed. Please try again!', '', {
|
|
|
|
duration: 800,
|
|
|
|
});
|
2017-03-26 13:32:29 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2017-04-10 22:14:40 +01:00
|
|
|
|
|
|
|
function leftAlign(text) {
|
|
|
|
let indent = Number.MAX_VALUE;
|
|
|
|
const lines = text.split('\n');
|
|
|
|
lines.forEach(line => {
|
|
|
|
const lineIndent = line.search(/\S/);
|
|
|
|
if (lineIndent !== -1) {
|
|
|
|
indent = Math.min(lineIndent, indent);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return lines.map(line => line.substr(indent)).join('\n').trim();
|
|
|
|
}
|