Refactor markdown renderer

This commit is contained in:
Roman Hotsiy 2016-10-30 17:56:24 +02:00
parent 44cd7784aa
commit c39166266c
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
5 changed files with 111 additions and 74 deletions

View File

@ -1,25 +1,4 @@
'use strict'; 'use strict';
import * as Remarkable from 'remarkable';
declare var Prism: any;
const md = new Remarkable({
html: true,
linkify: true,
breaks: false,
typographer: false,
highlight: (str, lang) => {
if (lang === 'json') lang = 'js';
let grammar = Prism.languages[lang];
//fallback to clike
if (!grammar) return str;
return Prism.highlight(str, grammar);
}
});
interface HeadersHandler {
open: Function;
close: Function;
}
export function stringify(obj:any) { export function stringify(obj:any) {
return JSON.stringify(obj); return JSON.stringify(obj);
@ -34,41 +13,7 @@ export function isFunction(func: any) {
} }
export function isBlank(obj: any): boolean { export function isBlank(obj: any): boolean {
return obj == null; return obj == undefined;
}
export function renderMd(rawText:string, headersHandler?:HeadersHandler) {
let _origRule;
if (headersHandler) {
_origRule = {
open: md.renderer.rules.heading_open,
close: md.renderer.rules.heading_close
};
md.renderer.rules.heading_open = (tokens, idx) => {
if (tokens[idx].hLevel !== 1 ) {
return _origRule.open(tokens, idx);
} else {
return headersHandler.open(tokens, idx);
}
};
md.renderer.rules.heading_close = (tokens, idx) => {
if (tokens[idx].hLevel !== 1 ) {
return _origRule.close(tokens, idx);
} else {
return headersHandler.close(tokens, idx);
}
};
}
let res = md.render(rawText);
if (headersHandler) {
md.renderer.rules.heading_open = _origRule.open;
md.renderer.rules.heading_close = _origRule.close;
}
return res;
} }
export function statusCodeType(statusCode) { export function statusCodeType(statusCode) {

View File

@ -1 +1,3 @@
export * from './custom-error-handler'; export * from './custom-error-handler';
export * from './helpers';
export * from './md-renderer';

95
lib/utils/md-renderer.ts Normal file
View File

@ -0,0 +1,95 @@
'use strict';
import { Injectable } from '@angular/core';
import * as slugify from 'slugify';
import * as Remarkable from 'remarkable';
import { SecurityDefinitions } from '../components/';
declare var Prism: any;
const md = new Remarkable({
html: true,
linkify: true,
breaks: false,
typographer: false,
highlight: (str, lang) => {
if (lang === 'json') lang = 'js';
let grammar = Prism.languages[lang];
//fallback to clike
if (!grammar) return str;
return Prism.highlight(str, grammar);
}
});
interface HeadersHandler {
open: Function;
close: Function;
}
@Injectable()
export class MdRenderer {
private _origRules:any = {};
private _preProcessors:Function[] = [];
public firstLevelHeadings: string[] = [];
constructor(private raw: boolean = false) {
// TODO
if (!raw) {
this.addPreprocessor(SecurityDefinitions.insertTagIntoDescription);
}
}
addPreprocessor(p: Function) {
this._preProcessors.push(p);
}
saveOrigRules() {
this._origRules.open = md.renderer.rules.heading_open;
this._origRules.close = md.renderer.rules.heading_close;
}
restoreOrigRules() {
md.renderer.rules.heading_open = this._origRules.open;
md.renderer.rules.heading_close = this._origRules.close;
}
headingOpenRule(tokens, idx) {
if (tokens[idx].hLevel !== 1 ) {
return this._origRules.open(tokens, idx);
} else {
let content = tokens[idx + 1].content;
this.firstLevelHeadings.push(content);
let contentSlug = slugify(content);
return `<h${tokens[idx].hLevel} section="section/${contentSlug}">` +
`<a class="share-link" href="#section/${contentSlug}"></a>`;
}
}
headingCloseRule(tokens, idx) {
if (tokens[idx].hLevel !== 1 ) {
return this._origRules.close(tokens, idx);
} else {
return `</h${tokens[idx].hLevel}>\n`;
}
}
renderMd(rawText:string) {
if (!this.raw) {
this.saveOrigRules();
md.renderer.rules.heading_open = this.headingOpenRule.bind(this);
md.renderer.rules.heading_close = this.headingCloseRule.bind(this);
}
let text = rawText;
for (let i=0; i<this._preProcessors.length; i++) {
text = this._preProcessors[i](text);
}
let res = md.render(text);
if (!this.raw) {
this.restoreOrigRules();
}
return res;
}
}

View File

@ -4,7 +4,7 @@ import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { isString, stringify, isBlank } from './helpers'; import { isString, stringify, isBlank } from './helpers';
import JsonPointer from './JsonPointer'; import JsonPointer from './JsonPointer';
import { renderMd } from './helpers'; import { MdRenderer} from './';
import { JsonFormatter } from './JsonFormatterPipe'; import { JsonFormatter } from './JsonFormatterPipe';
declare var Prism: any; declare var Prism: any;
@ -58,7 +58,10 @@ export class JsonPointerEscapePipe implements PipeTransform {
@Pipe({ name: 'marked' }) @Pipe({ name: 'marked' })
export class MarkedPipe implements PipeTransform { export class MarkedPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {} renderer: MdRenderer;
constructor(private sanitizer: DomSanitizer) {
this.renderer = new MdRenderer(true);
}
transform(value:string) { transform(value:string) {
if (isBlank(value)) return value; if (isBlank(value)) return value;
if (!isString(value)) { if (!isString(value)) {
@ -66,7 +69,7 @@ export class MarkedPipe implements PipeTransform {
} }
return this.sanitizer.bypassSecurityTrustHtml( return this.sanitizer.bypassSecurityTrustHtml(
`<span class="redoc-markdown-block">${renderMd(value)}</span>` `<span class="redoc-markdown-block">${this.renderer.renderMd(value)}</span>`
); );
} }
} }
@ -125,5 +128,5 @@ export class EncodeURIComponentPipe implements PipeTransform {
} }
export const REDOC_PIPES = [ export const REDOC_PIPES = [
JsonPointerEscapePipe, MarkedPipe, SafePipe, PrismPipe, EncodeURIComponentPipe, JsonFormatter JsonPointerEscapePipe, MarkedPipe, SafePipe, PrismPipe, EncodeURIComponentPipe, JsonFormatter, KeysPipe
]; ];

View File

@ -2,11 +2,11 @@
import * as JsonSchemaRefParser from 'json-schema-ref-parser'; import * as JsonSchemaRefParser from 'json-schema-ref-parser';
import { JsonPointer } from './JsonPointer'; import { JsonPointer } from './JsonPointer';
import { renderMd, safePush } from './helpers';
import * as slugify from 'slugify';
import { parse as urlParse, resolve as urlResolve } from 'url'; import { parse as urlParse, resolve as urlResolve } from 'url';
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { MdRenderer } from './';
export class SpecManager { export class SpecManager {
public _schema: any = {}; public _schema: any = {};
public apiUrl: string; public apiUrl: string;
@ -75,18 +75,10 @@ export class SpecManager {
} }
preprocess() { preprocess() {
this._schema.info['x-redoc-html-description'] = renderMd( this._schema.info.description, { let mdRender = new MdRenderer();
open: (tokens, idx) => { if (!this._schema.info.description) this._schema.info.description = '';
let content = tokens[idx + 1].content; this._schema.info['x-redoc-html-description'] = mdRender.renderMd(this._schema.info.description);
safePush(this._schema.info, 'x-redoc-markdown-headers', content); this._schema.info['x-redoc-markdown-headers'] = mdRender.firstLevelHeadings;
content = slugify(content);
return `<h${tokens[idx].hLevel} section="section/${content}">` +
`<a class="share-link" href="#section/${content}"></a>`;
},
close: (tokens, idx) => {
return `</h${tokens[idx].hLevel}>`;
}
});
} }
get schema() { get schema() {