From 3311d4a942849e72a2952200ac24043ab9849ecb Mon Sep 17 00:00:00 2001 From: Ed Morales Date: Mon, 23 Jan 2017 17:04:08 -0800 Subject: [PATCH] feature(highlight): enhancements and code-health. (#277) * bugfix(highlight:) Remove only empty lines at the beggining and end of the content so content new lines arent removed * check indentention on first code line as a guide * remove `html` lang workaround so code parsing is more generic now * fixed indendation in demo * bugfix(highlight): Use Renderer to limit access to native DOM and DomSanitizer to prevent XSS issues. * feature(highlight): Added [content] input to td-highlight to load content dynamically. * load README.md in highlight docs * update README.md for highlight * remove info thats already in README.md * update docblock in markdown * polish README.md * move scss rules so we can apply any pre-built theme instead of the covalent-theme * update themeing in markdown and highlight * comment pre-built theme in theme.scss * feature(highlight): Mimicking VS Dark+ theme as closely as possible by default in covalent-highlight-theme * changed color for regexp and link * code-health(highlight): Added initial set of unit-tests. * wrong title in README.md * replace npm code block with bash * fix README.md in markdown * point to the main coveralls badge instead of develop branch * remove methods and use regexp to remove leading and trailing new empty lines * feature(markdown): Remove only empty lines at the beginning and end of code content only * fixed indentation * fixed unit test typo --- README.md | 2 +- .../highlight/highlight.component.html | 131 +++++-------- .../highlight/highlight.component.ts | 22 ++- src/platform/highlight/README.md | 179 ++++++++++-------- src/platform/highlight/_highlight-theme.scss | 85 +++++---- .../highlight/highlight.component.html | 6 +- .../highlight/highlight.component.scss | 73 ++++--- .../highlight/highlight.component.spec.ts | 133 +++++++++++++ src/platform/highlight/highlight.component.ts | 103 +++++++--- src/platform/markdown/README.md | 37 +++- src/platform/markdown/markdown.component.ts | 29 ++- src/theme.scss | 2 +- 12 files changed, 504 insertions(+), 298 deletions(-) create mode 100644 src/platform/highlight/highlight.component.spec.ts diff --git a/README.md b/README.md index 3c9a5dda84..7fc6908cbe 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![npm version](https://badge.fury.io/js/%40covalent%2Fcore.svg)](https://badge.fury.io/js/%40covalent%2Fcore) [![Join the chat at https://gitter.im/Teradata/covalent](https://badges.gitter.im/Teradata/covalent.svg)](https://gitter.im/Teradata/covalent?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Dependency Status](https://dependencyci.com/github/Teradata/covalent/badge)](https://dependencyci.com/github/Teradata/covalent) -[![Coverage Status](https://coveralls.io/repos/github/Teradata/covalent/badge.svg?branch=develop)](https://coveralls.io/github/Teradata/covalent?branch=develop) +[![Coverage Status](https://coveralls.io/repos/github/Teradata/covalent/badge.svg)](https://coveralls.io/github/Teradata/covalent) Covalent diff --git a/src/app/components/components/highlight/highlight.component.html b/src/app/components/components/highlight/highlight.component.html index 0128fe7f54..60789bc19a 100644 --- a/src/app/components/components/highlight/highlight.component.html +++ b/src/app/components/components/highlight/highlight.component.html @@ -3,110 +3,73 @@ Highlighting your code snippets -

Simply wrap your code snippets in ]]>

-

To use HTML brackets < and > wrap the code with <![CDATA[ and ]]> or replace with HTMLs character entities

-

Also, to display model binding, add spaces between curly braces like:{{ '{' }} {{ '{' }} } } and wrap them with <![CDATA[ and ]]>

Example:

+

HTML usage:

-

hello world!

{ {property} }
- ]]> + ]]>

here's what CSS looks like:

- - pre {{'{'}} - display: block; - overflow-x: auto; - padding: 0; - margin: 0; - background: #002451; - color: white; - font-family: Menlo, Monaco, "Andale Mono", "lucida console", "Courier New", monospace; - line-height: 1.45; - tab-size: 2; - -webkit-font-smoothing: auto; - -webkit-text-size-adjust: none; - position: relative; - border-radius: 2px; - } + + -

and JavaScript (typescript):

+

and TypeScript (javascript):

} = {}; - private _observables: {[key: string]: Observable} = {}; + private _sources: {[key : string]: Subject} = {}; + private _observables: {[key: string]: Observable} = {}; - constructor(){ + constructor(){ - } + } - public register(name) : Observable { - this._sources[name] = new Subject(); - this._observables[name] = this._sources[name].asObservable(); - return this._observables[name]; - } + public register(name) : Observable { + this._sources[name] = new Subject(); + this._observables[name] = this._sources[name].asObservable(); + return this._observables[name]; + } - public emit(name: string): void { - if(this._sources[name]){ - this._sources[name].next(null); + public emit(name: string): void { + if(this._sources[name]){ + this._sources[name].next(null); + } } } - } - ]]> - -

Setup:

-

highlight.js needs to be added as vendor (installed as a highlight.js dependency).

- - - -

Reference the module in the system-config.js file.

- - - -

Then, import the [CovalentHighlightModule] using the forRoot() method in your NgModule:

- -
+ + + + + + + diff --git a/src/app/components/components/highlight/highlight.component.ts b/src/app/components/components/highlight/highlight.component.ts index f3cc4a63ac..bdcfc0c4a4 100644 --- a/src/app/components/components/highlight/highlight.component.ts +++ b/src/app/components/components/highlight/highlight.component.ts @@ -1,4 +1,5 @@ -import { Component, HostBinding } from '@angular/core'; +import { Component, HostBinding, OnInit } from '@angular/core'; +import { Http, Response } from '@angular/http'; import { slideInDownAnimation } from '../../../app.animations'; @@ -8,9 +9,26 @@ import { slideInDownAnimation } from '../../../app.animations'; templateUrl: './highlight.component.html', animations: [slideInDownAnimation], }) -export class HighlightDemoComponent { +export class HighlightDemoComponent implements OnInit { @HostBinding('@routeAnimation') routeAnimation: boolean = true; @HostBinding('class.td-route-animation') classAnimation: boolean = true; + content: string; + + constructor(private _http: Http) {} + + ngOnInit(): void { + let errorString: string = 'Warning: Resource could not be loaded.'; + this._http.get('platform/highlight/README.md').subscribe((res: Response) => { + try { + this.content = res.text(); + } catch (e) { + this.content = errorString; + } + }, (error: Error) => { + this.content = errorString; + }); + } + } diff --git a/src/platform/highlight/README.md b/src/platform/highlight/README.md index 1d96da240b..ff5269de63 100644 --- a/src/platform/highlight/README.md +++ b/src/platform/highlight/README.md @@ -1,8 +1,8 @@ -# td-highlight +## TdHighlightComponent: td-highlight -`td-highlight` is an HTML wrapper `
` tags enhanced with styling and code parser for angular2 on top of the [highlightjs lib](https://highlightjs.org/).
+`td-highlight` is an @angular component that uses native **@angular** to parse code to HTML elements. It is based on [highlight.js](https://highlightjs.org/) library.
 
-This implementation supports all the Common languages in highlightjs and typescript.
+This implementation supports all the Common languages in *highlight.js*.
 
 ## API Summary
 
@@ -10,48 +10,30 @@ Properties:
 
 | Name | Type | Description |
 | --- | --- | --- |
-| `lang` | `"typescript"|"html"|"css"|[any common language supported in highlightjs]` | The language of the code thats inside the component.
+| `lang` | `[any common language supported in highlight.js]` | Language of the code content to be parsed as highlighted html.
+| `content` | `string` | Code content to be parsed as highlighted html. Used to load data dynamically. e.g. `.ts` content.
 
-## Installation
+**Note:** This module uses the **DomSanitizer** service to ~sanitize~ the parsed `html` from the `highlight.js` lib to avoid **XSS** issues.
 
-This component can be installed as npm package and can be included by importing from `@covalent/highlight`.
+By default, `--dev` build will log the following message in the console to let you know:
 
-## Setup
+`WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).`
 
-`highlight.js` needs to be added as vendor (installed as a `highlight.js` dependency).
+## Installation
 
-```typescript
-module.exports = function(defaults) {
-  return new Angular2App(defaults, {
-    vendorNpmFiles: [
-      ...
-      'highlight.js/lib/**'
-    ]
-  });
-};
-```
-Reference the module in the `system-config.js` file.
+This component can be installed as npm package.
 
-```typescript
-  
+```bash
+npm i -save @covalent/highlight
 ```
 
-Then, import the [CovalentHighlightModule] using the forRoot() method in your NgModule:
+## Setup
+
+Import the **[CovalentHighlightModule]** using the *forRoot()* method in your NgModule:
 
 ```typescript
 import { CovalentHighlightModule } from '@covalent/highlight';
+
 @NgModule({
   imports: [
     CovalentHighlightModule.forRoot(),
@@ -62,75 +44,110 @@ import { CovalentHighlightModule } from '@covalent/highlight';
 export class MyModule {}
 ```
 
+### Theming
+
+The `highlight` module comes with its own default `covalent` theme which you can use by importing our theme scss file.
+
+```css
+@import '~@covalent/highlight/highlight-theme';
+
+@include covalent-highlight-theme();
+```
+
+Alternatively, you can use the *highlight.js* pre-built [themes](https://github.com/isagalaev/highlight.js/tree/master/src/styles) by loading them either by an import:
+
+```css
+@import '~highlight.js/styles/vs.css';
+```
+
+Loading them in the `angular-cli.json`:
+
+```json
+"styles": [
+  "/path/to/node_modules/highlight.js/styles/vs.css"
+]
+```
+
+Or by loading them in the `index.html` file:
+
+```html
+
+```
+
 ## Usage
 
-Simply wrap your code snippets in ``. To use HTML brackets `<` and `>` wrap the code with `;` or replace with HTMLs character entities `<` and `>`. 
+Simply wrap your code snippets in ``. To use HTML brackets `<` and `>` wrap the code with `;` or replace with HTMLs character entities `<` and `>`.
+
 Also, to display model binding, add spaces between curly braces like: `{ { } }` and wrap them  with `;`
 
-Example for HTML usage:
+Example for **HTML** usage:
 
- ```html
-       
+```html
+
   hello world!
-    { {property} }
+    
+      

hello world!

+ { {property} } +
]]>
- ``` - -Example for CSS usage: - - ```html - - pre { - display: block; - overflow-x: auto; - padding: 0; - margin: 0; - background: #002451; - color: white; - font-family: Menlo, Monaco, "Andale Mono", "lucida console", "Courier New", monospace; - line-height: 1.45; - tab-size: 2; - -webkit-font-smoothing: auto; - -webkit-text-size-adjust: none; - position: relative; - border-radius: 2px; - } +``` + +Example for **CSS** usage: + +```html + + - ``` +``` -Example for Typescript: +Example for **Typescript**: ```html } = {}; - private _observables: {[key: string]: Observable} = {}; + private _sources: {[key : string]: Subject} = {}; + private _observables: {[key: string]: Observable} = {}; - constructor(){ + constructor(){ - } + } - public register(name) : Observable { - this._sources[name] = new Subject(); - this._observables[name] = this._sources[name].asObservable(); - return this._observables[name]; - } + public register(name) : Observable { + this._sources[name] = new Subject(); + this._observables[name] = this._sources[name].asObservable(); + return this._observables[name]; + } - public emit(name: string): void { - if(this._sources[name]){ - this._sources[name].next(null); + public emit(name: string): void { + if(this._sources[name]){ + this._sources[name].next(null); + } } } - } ]]> ``` diff --git a/src/platform/highlight/_highlight-theme.scss b/src/platform/highlight/_highlight-theme.scss index bd7ec88d6d..8cdeee0b1b 100644 --- a/src/platform/highlight/_highlight-theme.scss +++ b/src/platform/highlight/_highlight-theme.scss @@ -1,73 +1,90 @@ -@import '../core/common/styles/variables'; @import '~@angular/material/core/theming/theming'; +/** +* Mimicking VS Dark+ theme as closely as possible +*/ @mixin covalent-highlight-theme() { td-highlight { + background: #1E1E21; .highlight { - color: md-color($md-indigo, 50); - } - padding: $padding; - & + :host { - margin-top: $margin; /* 2nd neighbor & beyond will have margin */ + color: md-color($md-grey, 50); } .hljs-comment, .hljs-quote { - color: md-color($md-purple, 200); + color: #608b4e; } - .hljs-variable, - .hljs-template-variable, .hljs-tag, .hljs-name, + .hljs-selector-tag, + .hljs-keyword, + .hljs-literal{ + color: #569cd6; + } + + .hljs-attr, + //.hljs-params, // Remove param color for now + .hljs-attribute { + color: #9cdcfe; + } + + .hljs-string, + .hljs-selector-attr, + .hljs-regexp, + .hljs-link { + color: #ce9178; + } + .hljs-selector-id, .hljs-selector-class, - .hljs-regexp, - .hljs-deletion { - color: md-color($md-pink, A100); + .hljs-selector-pseudo { + color: #d7ba7d; + } + + .hljs-meta { + color: #DCDCAA; } - .hljs-number, .hljs-built_in, .hljs-builtin-name, - .hljs-literal, .hljs-type, - .hljs-params, - .hljs-meta, - .hljs-link { - color: md-color($md-orange, 200); - } - - .hljs-attribute { - color: md-color($md-deep-orange, 200); + .hljs-section, + .hljs-class .hljs-title, + .hljs-symbol, + .hljs-bullet { + color: #4EC9B0 } - .hljs-string, - .hljs-symbol, - .hljs-bullet, - .hljs-addition { - color: md-color($md-lime, 200); + .hljs-number { + color: #b5cea8; } - .hljs-title, - .hljs-section { - color: md-color($md-light-blue, 200); + .hljs-variable, + .hljs-template-variable { + color: #660; } - .hljs-keyword { - color: md-color($md-teal, A200); + .hljs-deletion { + color: #ffc8bd; } - .hljs-selector-tag { - color: md-color($md-blue, A100); + .hljs-addition { + background-color: #baeeba; } .hljs-emphasis { font-style: italic; } + .hljs-doctag, .hljs-strong { font-weight: bold; } + + .hljs-formula { + background-color: #eee; + font-style: italic; + } } } diff --git a/src/platform/highlight/highlight.component.html b/src/platform/highlight/highlight.component.html index e210fd0718..95a0b70bdc 100644 --- a/src/platform/highlight/highlight.component.html +++ b/src/platform/highlight/highlight.component.html @@ -1,5 +1 @@ -
-  
-    
-  
-
\ No newline at end of file + \ No newline at end of file diff --git a/src/platform/highlight/highlight.component.scss b/src/platform/highlight/highlight.component.scss index 3a977c77a2..000de58283 100644 --- a/src/platform/highlight/highlight.component.scss +++ b/src/platform/highlight/highlight.component.scss @@ -1,45 +1,42 @@ $code-font: Menlo, Monaco, "Andale Mono", "lucida console", "Courier New", monospace; +$padding: 16px; -pre, -code, -.highlight { - font-family: $code-font; -} -pre { +:host /deep/ { display: block; overflow-x: auto; - padding: 0; - margin: 0; - background: transparent; - color: white; - font-family: $code-font; - line-height: 1.45; - tab-size: 2; - -webkit-font-smoothing: auto; - -webkit-text-size-adjust: none; - position: relative; - border-radius: 2px; - font-size: 0.8rem; -} - -code { - margin: 0; - padding: 0; - overflow-wrap: break-word; - white-space: pre-wrap; -} + padding: $padding; + pre, + code, + .highlight { + font-family: $code-font; + } + pre { + display: block; + overflow-x: auto; + padding: 0; + margin: 0; + background: transparent; + font-family: $code-font; + line-height: 1.45; + tab-size: 2; + -webkit-font-smoothing: auto; + -webkit-text-size-adjust: none; + position: relative; + border-radius: 2px; + font-size: 0.8rem; + } -.highlight { - display: block; - overflow-wrap: break-word; - line-height: 1.5; - margin: 0; -} + code { + margin: 0; + padding: 0; + overflow-wrap: break-word; + white-space: pre-wrap; + } -/* Use host as a parent here since hljs renders after dom & doesn't get Angular shadowdom */ -:host { - display: block; - overflow-x: auto; - background: #292348; - color: white; + .highlight { + display: block; + overflow-wrap: break-word; + line-height: 1.5; + margin: 0; + } } diff --git a/src/platform/highlight/highlight.component.spec.ts b/src/platform/highlight/highlight.component.spec.ts new file mode 100644 index 0000000000..f11df759cd --- /dev/null +++ b/src/platform/highlight/highlight.component.spec.ts @@ -0,0 +1,133 @@ +import { + TestBed, + inject, + async, + ComponentFixture, +} from '@angular/core/testing'; +import 'hammerjs'; +import { Component } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { CovalentHighlightModule } from './'; + +describe('Component: Highlight', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CovalentHighlightModule.forRoot(), + ], + declarations: [ + TdHighlightEmptyTestComponent, + TdHighlightStaticHtmlTestComponent, + TdHighlightDynamicCssTestComponent, + TdHighlightUndefinedLangTestComponent, + ], + }); + TestBed.compileComponents(); + })); + + it('should render empty', async(inject([], () => { + + let fixture: ComponentFixture = TestBed.createComponent(TdHighlightEmptyTestComponent); + let component: TdHighlightEmptyTestComponent = fixture.debugElement.componentInstance; + let element: HTMLElement = fixture.nativeElement; + + expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim()) + .toBe(``); + expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy(); + expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim()).toBe(''); + }); + }))); + + it('should render code from static content', async(inject([], () => { + + let fixture: ComponentFixture = TestBed.createComponent(TdHighlightStaticHtmlTestComponent); + let component: TdHighlightStaticHtmlTestComponent = fixture.debugElement.componentInstance; + let element: HTMLElement = fixture.nativeElement; + + expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim()) + .toContain(`{ {property} }`.trim()); + expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeTruthy(); + expect(element.querySelector('td-highlight pre code').textContent.trim()).toContain(`{{property}}`); + expect(element.querySelectorAll('.hljs-tag').length).toBe(6); + }); + }))); + + it('should render code from dynamic content', async(inject([], () => { + + let fixture: ComponentFixture = TestBed.createComponent(TdHighlightDynamicCssTestComponent); + let component: TdHighlightDynamicCssTestComponent = fixture.debugElement.componentInstance; + component.content = ` + pre { + background: #002451; + border-radius: 2px; + }`; + let element: HTMLElement = fixture.nativeElement; + + expect(fixture.debugElement.query(By.css('td-highlight')).nativeElement.textContent.trim()) + .toBe(''); + expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeFalsy(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('td-highlight pre code'))).toBeTruthy(); + expect(element.querySelectorAll('.hljs-number').length).toBe(2); + }); + }))); + + it('should throw error for undefined language', async(inject([], () => { + let fixture: ComponentFixture = TestBed.createComponent(TdHighlightUndefinedLangTestComponent); + let component: TdHighlightUndefinedLangTestComponent = fixture.debugElement.componentInstance; + expect(function(): void { + fixture.detectChanges(); + }).toThrowError(); + }))); + +}); + +@Component({ + template: ` + + `, +}) +class TdHighlightEmptyTestComponent { +} + +@Component({ + template: ` + +

hello world!

+ { {property} } +
+ ]]> +
`, +}) +class TdHighlightStaticHtmlTestComponent { +} + +@Component({ + template: ` + + `, +}) +class TdHighlightDynamicCssTestComponent { + content: string; +} + +@Component({ + template: ` + + `, +}) +class TdHighlightUndefinedLangTestComponent { + lang: string; +} diff --git a/src/platform/highlight/highlight.component.ts b/src/platform/highlight/highlight.component.ts index 75290c673c..1b7df38368 100644 --- a/src/platform/highlight/highlight.component.ts +++ b/src/platform/highlight/highlight.component.ts @@ -1,4 +1,5 @@ -import { Component, AfterViewInit, ViewChild, ElementRef, Input, Renderer } from '@angular/core'; +import { Component, AfterViewInit, ElementRef, Input, Renderer, SecurityContext } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; /* tslint:disable-next-line */ let hljs: any = require('highlight.js/lib'); @@ -9,56 +10,98 @@ let hljs: any = require('highlight.js/lib'); }) export class TdHighlightComponent implements AfterViewInit { - @Input('lang') language: string = 'javascript'; + private _content: string; - @ViewChild('highlight') content: ElementRef; + /** + * content?: string + * + * Code content to be parsed as highlighted html. + * Used to load data dynamically. + * + * e.g. `.html`, `.ts` , etc. + */ + @Input('content') + set content(content: string) { + this._content = content; + this._loadContent(this._content); + } - constructor(private renderer: Renderer) { + /** + * lang?: string + * + * Language of the code content to be parsed as highlighted html. + * Defaults to `typescript` + * + * e.g. `typescript`, `html` , etc. + */ + @Input('lang') language: string = 'typescript'; - } + constructor(private _renderer: Renderer, + private _elementRef: ElementRef, + private _domSanitizer: DomSanitizer) {} ngAfterViewInit(): void { if (!this.language) { throw new Error('Error: language attribute must be defined in TdHighlightComponent.'); } - let codeElement: HTMLElement = this.content.nativeElement; - let code: string = codeElement.innerHTML; - this.renderer.detachView([].slice.call(codeElement.childNodes)); - this.render(code, codeElement); + if (!this._content) { + this._loadContent((this._elementRef.nativeElement).textContent); + } + } + /** + * General method to parse a string of code into HTML Elements and load them into the container + */ + private _loadContent(code: string): void { + if (code && code.trim().length > 0) { + // Parse html string into actual HTML elements. + let preElement: HTMLPreElement = this._elementFromString(this._render(code)); + // Clean container + this._renderer.setElementProperty(this._elementRef.nativeElement, 'innerHTML', ''); + // Project DIV element into container + this._renderer.projectNodes(this._elementRef.nativeElement, [preElement]); + } + } + + private _elementFromString(codeStr: string): HTMLPreElement { + // Renderer doesnt have a parsing method, so we have to sanitize and use [innerHTML] + // to parse the string into DOM element for now. + const preElement: HTMLPreElement = this._renderer.createElement(this._elementRef.nativeElement, 'pre'); + const codeElement: HTMLElement = this._renderer.createElement(preElement, 'code'); + // Set .highlight class into element + this._renderer.setElementClass(codeElement, 'highlight', true); + codeElement.innerHTML = this._domSanitizer.sanitize(SecurityContext.HTML, codeStr); + return preElement; } - render(contents: string, codeElement: HTMLElement): void { + private _render(contents: string): string { + // Trim leading and trailing newlines + contents = contents.replace(/^(\s|\t)*\n+/g, '') + .replace(/(\s|\t)*\n+(\s|\t)*$/g, ''); + // Split markup by line characters let lines: string[] = contents.split('\n'); - // Remove empty lines - lines = lines.filter(function(line: string): boolean { - return line.trim().length > 0; - }); - // Make it so each line starts at 0 whitespace - let firstLineWhitespace: string = lines[0].match(/^\s*/)[0]; + // check how much indentation is used by the first actual code line + let firstLineWhitespace: string = lines[0].match(/^(\s|\t)*/)[0]; + + // Remove all indentation spaces so code can be parsed correctly let startingWhitespaceRegex: RegExp = new RegExp('^' + firstLineWhitespace); lines = lines.map(function(line: string): string { return line .replace('=""', '') // remove empty values .replace(startingWhitespaceRegex, '') - .replace(/\s+$/, ''); + .replace(/\s+$/, ''); // remove trailing white spaces }); - this.renderer.setElementClass(codeElement, 'highlight', true); - let codeToParse: string = lines.join('\n') .replace(/\{ \{/gi, '{{').replace(/\} \}/gi, '}}') .replace(/</gi, '<').replace(/>/gi, '>'); // replace with < and > to render HTML in angular 2 - if (this.language === 'html') { // need to use CDATA for HTML - this.renderer.createText(codeElement, codeToParse, undefined); - hljs.highlightBlock(codeElement); - } else { - let highlightedCode: any = hljs.highlight(this.language, codeToParse, true); - highlightedCode.value = highlightedCode.value - .replace(/=""<\/span>/gi, '') - .replace('', '') - .replace('', ''); - codeElement.innerHTML = highlightedCode.value; - } + + // Parse code with highlight.js depending on language + let highlightedCode: any = hljs.highlight(this.language, codeToParse, true); + highlightedCode.value = highlightedCode.value + .replace(/=""<\/span>/gi, '') + .replace('', '') + .replace('', ''); + return highlightedCode.value; } } diff --git a/src/platform/markdown/README.md b/src/platform/markdown/README.md index 0054bc0885..e74757c7e5 100644 --- a/src/platform/markdown/README.md +++ b/src/platform/markdown/README.md @@ -1,6 +1,6 @@ ## TdMarkdownComponent: td-markdown -`` is a directive for Github flavored Javascript Markdown to HTML converter. It is based on [showdown](https://github.com/showdownjs/showdown/) library. +`` is an @angular component for Github flavored Javascript Markdown to HTML converter. It is based on [showdown](https://github.com/showdownjs/showdown/) library. ## API Summary @@ -8,9 +8,9 @@ Methods: | Name | Type | Description | | --- | --- | --- | -| `content` | `string` | Markdown format content to be parsed as html markup. Used to load data dynamically. e.g. Resource file. +| `content` | `string` | Markdown format content to be parsed as html markup. Used to load data dynamically. e.g. `README.md` content. -**Note:** This module uses the **DomSanitizer** ng2 service to ~sanitize~ the parsed `html` from the `showdown` lib to avoid **XSS** issues. +**Note:** This module uses the **DomSanitizer** service to ~sanitize~ the parsed `html` from the `showdown` lib to avoid **XSS** issues. By default, `--dev` build will log the following message in the console to let you know: @@ -20,7 +20,7 @@ By default, `--dev` build will log the following message in the console to let y This component can be installed as npm package. -```npm +```bash npm i -save @covalent/markdown ``` @@ -32,14 +32,14 @@ npm i -save @covalent/markdown ```json "scripts": [ - "../node_modules/showdown/dist/showdown.js" + "path/to/node_modules/showdown/dist/showdown.js" ] ``` **index.html**: ```html - + ``` Then, import the **[CovalentMarkdownModule]** using the *forRoot()* method in your NgModule: @@ -56,6 +56,31 @@ import { CovalentMarkdownModule } from '@covalent/markdown'; export class MyModule {} ``` +### Theming + +The `markdown` module comes with its own `covalent` theme which uses the material *theme* which is used by importing our theme scss file. + +```css +@import '~@angular/material/core/theming/all-theme'; +@import '~@covalent/markdown/markdown-theme'; + +@include md-core(); + +$primary: md-palette($md-orange, 800); +$accent: md-palette($md-light-blue, 600, A100, A400); +$warn: md-palette($md-red, 600); + +$theme: md-light-theme($primary, $accent, $warn); + +@include markdown-markdown-theme($theme); +``` + +Or by loading them in the `index.html` file: + +```html + +``` + ## Example **Html:** diff --git a/src/platform/markdown/markdown.component.ts b/src/platform/markdown/markdown.component.ts index 56d1ff974c..dbd377d020 100644 --- a/src/platform/markdown/markdown.component.ts +++ b/src/platform/markdown/markdown.component.ts @@ -18,7 +18,7 @@ export class TdMarkdownComponent implements AfterViewInit { * Markdown format content to be parsed as html markup. * Used to load data dynamically. * - * e.g. Resource file. + * e.g. README.md content. */ @Input('content') set content(content: string) { @@ -58,34 +58,31 @@ export class TdMarkdownComponent implements AfterViewInit { return div; } - private _render(markup: string): string { - // Split markup by line characters - let lines: string[] = markup.split('\n'); + private _render(markdown: string): string { + // Trim leading and trailing newlines + markdown = markdown.replace(/^(\s|\t)*\n+/g, '') + .replace(/(\s|\t)*\n+(\s|\t)*$/g, ''); + // Split markdown by line characters + let lines: string[] = markdown.split('\n'); - // check how much indentation is used by the first actual markup line - let firstLineWhitespace: string = ''; - for (let line of lines) { - if (line && line.trim().length > 0) { - firstLineWhitespace = line.match(/^\s*/)[0]; - break; - } - } + // check how much indentation is used by the first actual markdown line + let firstLineWhitespace: string = lines[0].match(/^(\s|\t)*/)[0]; - // Remove all indentation spaces so markup can be parsed correctly + // Remove all indentation spaces so markdown can be parsed correctly let startingWhitespaceRegex: RegExp = new RegExp('^' + firstLineWhitespace); lines = lines.map(function(line: string): string { return line.replace(startingWhitespaceRegex, ''); }); // Join lines again with line characters - let codeToParse: string = lines.join('\n'); + let markdownToParse: string = lines.join('\n'); - // Convert markup into html + // Convert markdown into html let converter: any = new showdown.Converter(); converter.setOption('ghCodeBlocks', true); converter.setOption('tasklists', true); converter.setOption('tables', true); - let html: string = converter.makeHtml(codeToParse); + let html: string = converter.makeHtml(markdownToParse); return html; } diff --git a/src/theme.scss b/src/theme.scss index 997f9b277a..981dfb807a 100644 --- a/src/theme.scss +++ b/src/theme.scss @@ -28,7 +28,7 @@ $theme: md-light-theme($primary, $accent, $warn); @include covalent-theme($theme); @include covalent-markdown-theme($theme); @include covalent-charts-theme($theme); -@include covalent-highlight-theme(); +@include covalent-highlight-theme(); // OR @import '~highlight.js/styles/vs.css'; // Custom theme examples .blue-orange {