mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-10 19:06:34 +03:00
Pull markdown headers from description into side menu
This commit is contained in:
parent
82f1be1d11
commit
205aa6211c
|
@ -4,7 +4,28 @@ schemes:
|
|||
host: petstore.swagger.io
|
||||
basePath: /v2
|
||||
info:
|
||||
description: 'This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.'
|
||||
description: |
|
||||
This is a sample server Petstore server.
|
||||
You can find out more about Swagger at
|
||||
[http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).
|
||||
For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
# Introduction
|
||||
This API is documented in **OpenAPI format** and is based on
|
||||
[Pestore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
||||
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
||||
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
# OpenAPI Specification
|
||||
This API is documented in **OpenAPI format** and is based on
|
||||
[Pestore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
||||
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
||||
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
# Cross-Origin Resource Sharing
|
||||
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
||||
And that allows cross-domain communication from the browser.
|
||||
All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site.
|
||||
|
||||
version: 1.0.0
|
||||
title: Swagger Petstore
|
||||
termsOfService: 'http://swagger.io/terms/'
|
||||
|
@ -19,19 +40,6 @@ externalDocs:
|
|||
description: Find out how to create Github repo for your OpenAPI spec.
|
||||
url: 'https://github.com/Rebilly/generator-openapi-repo'
|
||||
tags:
|
||||
- name: Introduction
|
||||
x-traitTag: true
|
||||
description: 'This API is documented in **OpenAPI format** and is based on [Pestore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team. It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo) tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).'
|
||||
- name: OpenAPI Specification
|
||||
description: 'The goal of The OpenAPI Specification is to define a standard, language-agnostic interface to REST APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined via OpenAPI, a consumer can understand and interact with the remote service with a minimal amount of implementation logic. Similar to what interfaces have done for lower-level programming, OpenAPI removes the guesswork in calling the service.'
|
||||
externalDocs:
|
||||
description: Find out more
|
||||
url: 'https://openapis.org/'
|
||||
- name: Cross-Origin Resource Sharing
|
||||
description: |
|
||||
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
||||
And that allows cross-domain communication from the browser.
|
||||
All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site.
|
||||
- name: pet
|
||||
description: Everything about your Pets
|
||||
- name: store
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div>
|
||||
<h1 class="api-info-header">{{info.title}} ({{info.version}})</h1>
|
||||
<p *ngIf="info.description" [innerHtml]="info.description | marked"> </p>
|
||||
<p *ngIf="info.description" [innerHtml]="info['x-redoc-html-description'] | safe"> </p>
|
||||
<p>
|
||||
<!-- TODO: create separate components for contact and license ? -->
|
||||
<span *ngIf="info.contact"> Contact:
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
:host > div {
|
||||
width: 60%;
|
||||
padding: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a.openapi-button {
|
||||
|
@ -15,3 +17,7 @@ a.openapi-button {
|
|||
margin-left: 0.5em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
:host [section] {
|
||||
padding-top: 80px;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
import { Component } from '@angular/core';
|
||||
import { OptionsService } from '../../services/index';
|
||||
|
||||
import {
|
||||
inject,
|
||||
|
@ -17,8 +18,14 @@ describe('Redoc components', () => {
|
|||
let builder;
|
||||
let component;
|
||||
let fixture;
|
||||
let opts;
|
||||
|
||||
beforeEach(async(inject([TestComponentBuilder, SpecManager], (tcb, specMgr) => {
|
||||
beforeEach(async(inject([TestComponentBuilder, SpecManager, OptionsService], (tcb, specMgr, _opts) => {
|
||||
opts = _opts;
|
||||
opts.options = {
|
||||
scrollYOffset: () => 0,
|
||||
$scrollParent: window
|
||||
};
|
||||
builder = tcb;
|
||||
return specMgr.load('/tests/schemas/api-info-test.json');
|
||||
})));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import { SpecManager, RedocComponent, BaseComponent } from '../base';
|
||||
import { OptionsService } from '../../services/index';
|
||||
import { OptionsService, MenuService } from '../../services/index';
|
||||
|
||||
@RedocComponent({
|
||||
selector: 'api-info',
|
||||
|
@ -11,7 +11,7 @@ import { OptionsService } from '../../services/index';
|
|||
export class ApiInfo extends BaseComponent {
|
||||
info: any;
|
||||
specUrl: String;
|
||||
constructor(specMgr:SpecManager, private optionsService:OptionsService) {
|
||||
constructor(specMgr:SpecManager, private optionsService:OptionsService, private menuServ: MenuService) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@import '../../shared/styles/variables';
|
||||
@import '../../shared/styles/share-link';
|
||||
|
||||
|
||||
:host {
|
||||
padding-bottom: 100px;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<div class="methods">
|
||||
<div class="tag" *ngFor="let tag of tags;trackBy:trackByTagName">
|
||||
<div class="tag-info" [attr.tag]="tag.name" *ngIf="!tag.empty">
|
||||
<h1 class="sharable-header"> <a class="share-link" href="#tag/{{tag.name | encodeURIComponent}}"></a>{{tag.name}} </h1>
|
||||
<div class="tag-info" [attr.section]="tag.name" *ngIf="!tag.virtual">
|
||||
<h1 class="sharable-header"> <a class="share-link" href="#section/{{tag.name | encodeURIComponent}}"></a>{{tag.name}} </h1>
|
||||
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
|
||||
</div>
|
||||
<method *ngFor="let method of tag.methods;trackBy:trackByPointer" [pointer]="method.pointer" [attr.pointer]="method.pointer"
|
||||
[attr.tag]="method.tag" [tag]="method.tag" [attr.operation-id]="method.operationId"></method>
|
||||
[attr.section]="method.tag" [tag]="method.tag" [attr.operation-id]="method.operationId"></method>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
@import '../../shared/styles/variables';
|
||||
@import '../../shared/styles/share-link';
|
||||
|
||||
.tag-info {
|
||||
padding: 40px;
|
||||
|
@ -21,6 +20,7 @@
|
|||
color: $headers-color;
|
||||
text-transform: capitalize;
|
||||
font-weight: normal;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.methods {
|
||||
|
|
|
@ -15,25 +15,21 @@ import { SchemaHelper } from '../../services/index';
|
|||
detect: true
|
||||
})
|
||||
export class MethodsList extends BaseComponent {
|
||||
tags:Array<any>;
|
||||
tags:Array<any> = [];
|
||||
constructor(specMgr:SpecManager) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.tags = [];
|
||||
// follow SwaggerUI behavior for cases when one method has more than one tag:
|
||||
// duplicate methods
|
||||
|
||||
let tags = SchemaHelper.buildMenuTree(this.specMgr.schema);
|
||||
tags.forEach(tagInfo => {
|
||||
this.tags = tags.filter(tagInfo => !tagInfo.virtual);
|
||||
this.tags.forEach(tagInfo => {
|
||||
// inject tag name into method info
|
||||
tagInfo.methods = tagInfo.methods || [];
|
||||
tagInfo.methods.forEach(method => {
|
||||
method.tag = tagInfo.name;
|
||||
});
|
||||
});
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
trackByPointer(idx, el) {
|
||||
|
|
|
@ -51,10 +51,6 @@ api-info, .side-bar {
|
|||
padding: 10px 0;
|
||||
}
|
||||
|
||||
api-info {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
api-logo {
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
@ -150,6 +146,8 @@ api-logo {
|
|||
color: #383838;
|
||||
}
|
||||
}
|
||||
|
||||
@import '../../shared/styles/share-link';
|
||||
}
|
||||
|
||||
footer {
|
||||
|
|
|
@ -15,7 +15,8 @@ import { SideMenu } from '../SideMenu/side-menu';
|
|||
|
||||
import { StickySidebar } from '../../shared/components/index';
|
||||
import {SpecManager} from '../../utils/SpecManager';
|
||||
import { OptionsService, RedocEventsService } from '../../services/index';
|
||||
import { OptionsService, RedocEventsService, MenuService,
|
||||
ScrollService, Hash } from '../../services/index';
|
||||
|
||||
var dom = new BrowserDomAdapter();
|
||||
var _modeLocked = false;
|
||||
|
@ -25,7 +26,10 @@ var _modeLocked = false;
|
|||
providers: [
|
||||
SpecManager,
|
||||
BrowserDomAdapter,
|
||||
RedocEventsService
|
||||
RedocEventsService,
|
||||
ScrollService,
|
||||
Hash,
|
||||
MenuService,
|
||||
],
|
||||
templateUrl: './redoc.html',
|
||||
styleUrls: ['./redoc.css'],
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
</span>
|
||||
</div>
|
||||
<div #desktop id="resources-nav">
|
||||
<h5 class="menu-header"> API reference </h5>
|
||||
<h5 class="menu-header" (click)="onclk()"> API reference </h5>
|
||||
<div *ngFor="let cat of categories; let idx = index" class="menu-cat">
|
||||
|
||||
<label class="menu-cat-header" (click)="activateAndScroll(idx, -1)" [hidden]="cat.empty"
|
||||
<label class="menu-cat-header" (click)="activateAndScroll(idx, -1)" [hidden]="cat.headless"
|
||||
[ngClass]="{active: cat.active}"> {{cat.name}}</label>
|
||||
<ul class="menu-subitems" @itemAnimation="cat.active ? 'expanded' : 'collapsed'">
|
||||
<li *ngFor="let method of cat.methods; let methIdx = index"
|
||||
<li *ngFor="let method of cat.methods; trackBy:summary; let methIdx = index"
|
||||
[ngClass]="{active: method.active}"
|
||||
(click)="activateAndScroll(idx, methIdx)">
|
||||
{{method.summary}}
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('Redoc components', () => {
|
|||
testOptions = opts;
|
||||
testOptions.options = {
|
||||
scrollYOffset: () => 0,
|
||||
scrollParent: window
|
||||
$scrollParent: window
|
||||
};
|
||||
return specMgr.load('/tests/schemas/extended-petstore.yml');
|
||||
})));
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
'use strict';
|
||||
|
||||
import { ElementRef, ChangeDetectorRef } from '@angular/core';
|
||||
|
||||
import { BrowserDomAdapter } from '@angular/platform-browser/src/browser/browser_adapter';
|
||||
import { global } from '@angular/core/src/facade/lang';
|
||||
import { trigger, state, animate, transition, style } from '@angular/core';
|
||||
import { RedocComponent, BaseComponent, SpecManager } from '../base';
|
||||
import { ScrollService, Hash, MenuService, OptionsService } from '../../services/index';
|
||||
|
||||
import { MenuCategory } from '../../services/schema-helper.service';
|
||||
|
||||
@RedocComponent({
|
||||
selector: 'side-menu',
|
||||
templateUrl: './side-menu.html',
|
||||
providers: [ScrollService, MenuService, Hash],
|
||||
styleUrls: ['./side-menu.css'],
|
||||
detect: true,
|
||||
onPushOnly: false,
|
||||
|
@ -29,7 +31,7 @@ import { ScrollService, Hash, MenuService, OptionsService } from '../../services
|
|||
export class SideMenu extends BaseComponent {
|
||||
activeCatCaption: string;
|
||||
activeItemCaption: string;
|
||||
categories: any;
|
||||
categories: Array<MenuCategory>;
|
||||
|
||||
private options: any;
|
||||
private $element: any;
|
||||
|
@ -87,7 +89,7 @@ export class SideMenu extends BaseComponent {
|
|||
toggleMobileNav() {
|
||||
let dom = this.dom;
|
||||
let $overflowParent = (this.options.$scrollParent === global) ? dom.defaultDoc().body
|
||||
: this.$scrollParent.$scrollParent;
|
||||
: this.$scrollParent;
|
||||
if (dom.hasStyle(this.$resourcesNav, 'height')) {
|
||||
dom.removeStyle(this.$resourcesNav, 'height');
|
||||
dom.removeStyle($overflowParent, 'overflow-y');
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CORE_DIRECTIVES, JsonPipe, AsyncPipe } from '@angular/common';
|
||||
import { SpecManager } from '../utils/SpecManager';
|
||||
import { MarkedPipe, JsonPointerEscapePipe } from '../utils/pipes';
|
||||
import { MarkedPipe, JsonPointerEscapePipe, SafePipe } from '../utils/pipes';
|
||||
|
||||
export { SpecManager };
|
||||
|
||||
|
@ -50,7 +50,7 @@ function snapshot(obj) {
|
|||
export function RedocComponent(options) {
|
||||
let inputs = safeConcat(options.inputs, commonInputs);
|
||||
let directives = safeConcat(options.directives, CORE_DIRECTIVES);
|
||||
let pipes = safeConcat(options.pipes, [JsonPointerEscapePipe, MarkedPipe, JsonPipe, AsyncPipe]);
|
||||
let pipes = safeConcat(options.pipes, [JsonPointerEscapePipe, MarkedPipe, JsonPipe, AsyncPipe, SafePipe]);
|
||||
if (options.onPushOnly === undefined) options.onPushOnly = true;
|
||||
|
||||
return function decorator(target) {
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('Menu service', () => {
|
|||
});
|
||||
|
||||
it('should scroll to method when location hash is present [jp]', (done) => {
|
||||
let hash = '#tag/pet/paths/~1pet~1findByStatus/get';
|
||||
let hash = '#section/pet/paths/~1pet~1findByStatus/get';
|
||||
spyOn(menu, 'hashScroll').and.callThrough();
|
||||
spyOn(window, 'scrollTo').and.stub();
|
||||
hashService.changed.subscribe(() => {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Injectable, EventEmitter } from '@angular/core';
|
|||
import { ScrollService, INVIEW_POSITION } from './scroll.service';
|
||||
import { Hash } from './hash.service';
|
||||
import { SpecManager } from '../utils/SpecManager';
|
||||
import { SchemaHelper } from './schema-helper.service';
|
||||
import { SchemaHelper, MenuCategory } from './schema-helper.service';
|
||||
|
||||
const CHANGE = {
|
||||
NEXT : 1,
|
||||
|
@ -14,7 +14,7 @@ const CHANGE = {
|
|||
@Injectable()
|
||||
export class MenuService {
|
||||
changed: EventEmitter<any> = new EventEmitter();
|
||||
categories: any;
|
||||
categories: Array<MenuCategory>;
|
||||
|
||||
activeCatIdx: number = 0;
|
||||
activeMethodIdx: number = -1;
|
||||
|
@ -58,8 +58,8 @@ export class MenuService {
|
|||
this.categories[this.activeCatIdx].name);
|
||||
}
|
||||
|
||||
getMethodElByPtr(ptr, tag) {
|
||||
let selector = ptr ? `[pointer="${ptr}"][tag="${tag}"]` : `[tag="${tag}"]`;
|
||||
getMethodElByPtr(ptr, section) {
|
||||
let selector = ptr ? `[pointer="${ptr}"][section="${section}"]` : `[section="${section}"]`;
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
|
||||
|
@ -95,6 +95,7 @@ export class MenuService {
|
|||
_calcActiveIndexes(offset) {
|
||||
let menu = this.categories;
|
||||
let catCount = menu.length;
|
||||
if (!catCount) return [0, -1];
|
||||
let catLength = menu[this.activeCatIdx].methods.length;
|
||||
|
||||
let resMethodIdx = this.activeMethodIdx + offset;
|
||||
|
@ -140,7 +141,7 @@ export class MenuService {
|
|||
let ptr = decodeURIComponent(hash.substr(namespace.length + 1));
|
||||
if (namespace === 'operation') {
|
||||
$el = this.getMethodElByOperId(ptr);
|
||||
} else if (namespace === 'tag') {
|
||||
} else if (namespace === 'section') {
|
||||
let tag = ptr.split('/')[0];
|
||||
ptr = ptr.substr(tag.length);
|
||||
$el = this.getMethodElByPtr(ptr, tag);
|
||||
|
|
|
@ -59,9 +59,13 @@ describe('Spec Helper', () => {
|
|||
info.methods[0].summary.should.be.equal('test post');
|
||||
});
|
||||
|
||||
it('should map x-traitTag to empty section', () => {
|
||||
let info = menuTree[0];
|
||||
info.empty.should.be.true();
|
||||
});
|
||||
|
||||
it('should map x-traitTag to empty methods list', () => {
|
||||
let info = menuTree[0];
|
||||
info['x-traitTag'].should.be.true();
|
||||
info.methods.should.be.empty();
|
||||
});
|
||||
|
||||
|
|
|
@ -8,6 +8,22 @@ interface PropertyPreprocessOptions {
|
|||
skipReadOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface MenuMethod {
|
||||
active: boolean;
|
||||
summary: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
export interface MenuCategory {
|
||||
name: string;
|
||||
|
||||
active?: boolean;
|
||||
methods?: Array<MenuMethod>;
|
||||
description?: string;
|
||||
empty?: string;
|
||||
virtual?: boolean;
|
||||
}
|
||||
|
||||
const injectors = {
|
||||
general: {
|
||||
check: () => true,
|
||||
|
@ -225,17 +241,22 @@ export class SchemaHelper {
|
|||
(method.description && method.description.substring(0, 50)) || '<no description>';
|
||||
}
|
||||
|
||||
static buildMenuTree(schema) {
|
||||
static buildMenuTree(schema):Array<MenuCategory> {
|
||||
let tag2MethodMapping = {};
|
||||
|
||||
let definedTags = schema.tags || [];
|
||||
// add tags into map to preserve order
|
||||
for (let tag of definedTags) {
|
||||
for (let header of (<Array<string>>(schema.info && schema.info['x-redoc-markdown-headers'] || []))) {
|
||||
tag2MethodMapping[header] = {
|
||||
name: header, virtual: true, methods: []
|
||||
};
|
||||
}
|
||||
|
||||
for (let tag of schema.tags || []) {
|
||||
tag2MethodMapping[tag.name] = {
|
||||
'description': tag.description,
|
||||
'name': tag.name,
|
||||
'x-traitTag': tag['x-traitTag'],
|
||||
'methods': []
|
||||
name: tag.name,
|
||||
description: tag.description,
|
||||
headless: tag.name === '',
|
||||
empty: !!tag['x-traitTag'],
|
||||
methods: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -256,11 +277,11 @@ export class SchemaHelper {
|
|||
if (!tag2MethodMapping[tag]) {
|
||||
tagDetails = {
|
||||
name: tag,
|
||||
empty: tag === ''
|
||||
headless: tag === ''
|
||||
};
|
||||
tag2MethodMapping[tag] = tagDetails;
|
||||
}
|
||||
if (tagDetails['x-traitTag']) continue;
|
||||
if (tagDetails.empty) continue;
|
||||
if (!tagDetails.methods) tagDetails.methods = [];
|
||||
tagDetails.methods.push({
|
||||
pointer: methodPointer,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import JsonSchemaRefParser from 'json-schema-ref-parser';
|
||||
import JsonPointer from './JsonPointer';
|
||||
import { renderMd, safePush } from './helpers';
|
||||
|
||||
export class SpecManager {
|
||||
public _schema:any = {};
|
||||
|
@ -42,6 +43,22 @@ export class SpecManager {
|
|||
if (this.apiUrl.endsWith('/')) {
|
||||
this.apiUrl = this.apiUrl.substr(0, this.apiUrl.length - 1);
|
||||
}
|
||||
|
||||
this.preprocess();
|
||||
}
|
||||
|
||||
preprocess() {
|
||||
this._schema.info['x-redoc-html-description'] = renderMd( this._schema.info.description, {
|
||||
open: (tokens, idx) => {
|
||||
let content = tokens[idx + 1].content;
|
||||
safePush(this._schema.info, 'x-redoc-markdown-headers', content);
|
||||
return `<h${tokens[idx].hLevel} section="${content}">` +
|
||||
`<a class="share-link" href="#section/${content}"></a>`;
|
||||
},
|
||||
close: (tokens, idx) => {
|
||||
return `</h${tokens[idx].hLevel}>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get schema() {
|
||||
|
|
|
@ -1,4 +1,59 @@
|
|||
'use strict';
|
||||
import 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 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) {
|
||||
if (statusCode < 100 || statusCode > 599) {
|
||||
|
@ -29,3 +84,8 @@ export function defaults(target, src) {
|
|||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
export function safePush(obj, prop, val) {
|
||||
if (!obj[prop]) obj[prop] = [];
|
||||
obj[prop].push(val);
|
||||
}
|
||||
|
|
|
@ -5,24 +5,11 @@ import { DomSanitizationService } from '@angular/platform-browser';
|
|||
import { isString, stringify, isBlank } from '@angular/core/src/facade/lang';
|
||||
import { BaseException } from '@angular/core/src/facade/exceptions';
|
||||
import JsonPointer from './JsonPointer';
|
||||
import { renderMd } from './helpers';
|
||||
|
||||
declare var Prism: any;
|
||||
|
||||
import Remarkable from 'remarkable';
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
class InvalidPipeArgumentException extends BaseException {
|
||||
constructor(type, value) {
|
||||
|
@ -73,11 +60,24 @@ export class MarkedPipe implements PipeTransform {
|
|||
}
|
||||
|
||||
return this.sanitizer.bypassSecurityTrustHtml(
|
||||
`<span class="redoc-markdown-block">${md.render(value)}</span>`
|
||||
`<span class="redoc-markdown-block">${renderMd(value)}</span>`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({ name: 'safe' })
|
||||
export class SafePipe implements PipeTransform {
|
||||
constructor(private sanitizer: DomSanitizationService) {}
|
||||
transform(value) {
|
||||
if (isBlank(value)) return value;
|
||||
if (!isString(value)) {
|
||||
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value);
|
||||
}
|
||||
|
||||
return this.sanitizer.bypassSecurityTrustHtml(value);
|
||||
}
|
||||
}
|
||||
|
||||
const langMap = {
|
||||
'c++': 'cpp',
|
||||
'c#': 'csharp',
|
||||
|
|
|
@ -16,5 +16,16 @@
|
|||
"host": "petstore.swagger.io",
|
||||
"basePath": "/v2/",
|
||||
"schemes": ["http"],
|
||||
"paths": {}
|
||||
"paths": {
|
||||
"/pet": {
|
||||
"post": {
|
||||
"tags": ["tag1"],
|
||||
"summary": "tag1"
|
||||
},
|
||||
"put": {
|
||||
"tags": ["tag1", "traitTag"],
|
||||
"summary": "two tags",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user