mirror of
https://github.com/Redocly/redoc.git
synced 2025-01-31 18:14:07 +03:00
Refactor markdown renderer
This commit is contained in:
parent
44cd7784aa
commit
c39166266c
|
@ -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) {
|
||||||
|
|
|
@ -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
95
lib/utils/md-renderer.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
];
|
];
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user