mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-10 19:06:34 +03:00
Expanding nested schemas by absolute pointer
This commit is contained in:
parent
46f6b29547
commit
e164590fca
|
@ -676,6 +676,13 @@ definitions:
|
|||
description: Category name
|
||||
type: string
|
||||
minLength: 1
|
||||
sub:
|
||||
description: test sub Category
|
||||
type: object
|
||||
properties:
|
||||
prop1:
|
||||
type: string
|
||||
description: The best prop1
|
||||
xml:
|
||||
name: Category
|
||||
Dog:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import { Component, ElementRef, ViewContainerRef, OnDestroy, Input,
|
||||
import { Component, ElementRef, ViewContainerRef, OnDestroy, OnInit, Input,
|
||||
AfterViewInit, ComponentFactoryResolver, Renderer } from '@angular/core';
|
||||
|
||||
import { JsonSchema } from './json-schema';
|
||||
|
@ -15,8 +15,9 @@ var cache = {};
|
|||
template: '',
|
||||
styles: [':host { display:none }']
|
||||
})
|
||||
export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||
export class JsonSchemaLazy implements OnDestroy, OnInit, AfterViewInit {
|
||||
@Input() pointer: string;
|
||||
@Input() absolutePointer: string;
|
||||
@Input() auto: boolean;
|
||||
@Input() isRequestSchema: boolean;
|
||||
@Input() final: boolean = false;
|
||||
|
@ -78,6 +79,10 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
|||
Object.assign(instance, this);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.absolutePointer) this.absolutePointer = this.pointer;
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (!this.auto && !this.disableLazy) return;
|
||||
this.loadCached();
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
<div class="tuple-item">
|
||||
<span class="tuple-item-index"> [{{idx}}]: </span>
|
||||
<json-schema class="nested-schema" [pointer]="item._pointer"
|
||||
[absolutePointer]="item._pointer"
|
||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema">
|
||||
</json-schema>
|
||||
</div>
|
||||
|
@ -92,9 +93,9 @@
|
|||
</tr>
|
||||
<tr class="param-schema" [ngClass]="{'last': last}" [hidden]="!prop._pointer">
|
||||
<td colspan="2">
|
||||
<zippy #subSchema title="Expand" [headless]="true" (open)="lazySchema.load()" [visible]="autoExpand">
|
||||
<json-schema-lazy #lazySchema [auto]="autoExpand" class="nested-schema" [pointer]="prop._pointer"
|
||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema">
|
||||
<zippy [attr.disabled]="prop._name" #subSchema title="Expand" [headless]="true" (openChange)="lazySchema.load()" [(open)]="prop.expanded">
|
||||
<json-schema-lazy #lazySchema [auto]="prop.expanded" class="nested-schema" [pointer]="prop._pointer"
|
||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema" absolutePointer="{{absolutePointer}}/properties/{{prop._name}}">
|
||||
</json-schema-lazy>
|
||||
</zippy>
|
||||
</td>
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
'use strict';
|
||||
|
||||
import { Component, Input, Renderer, ElementRef, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Component,
|
||||
Input,
|
||||
Renderer,
|
||||
ElementRef,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
import { SchemaNormalizer, SchemaHelper } from '../../services/index';
|
||||
import { BaseSearchableComponent, SpecManager } from '../base';
|
||||
import { SchemaNormalizer, SchemaHelper, AppStateService } from '../../services/';
|
||||
import { JsonPointer } from '../../utils/';
|
||||
import { Zippy } from '../../shared/components';
|
||||
import { JsonSchemaLazy } from './json-schema-lazy';
|
||||
|
||||
@Component({
|
||||
selector: 'json-schema',
|
||||
|
@ -11,8 +21,9 @@ import { SchemaNormalizer, SchemaHelper } from '../../services/index';
|
|||
styleUrls: ['./json-schema.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class JsonSchema extends BaseComponent implements OnInit {
|
||||
export class JsonSchema extends BaseSearchableComponent implements OnInit {
|
||||
@Input() pointer: string;
|
||||
@Input() absolutePointer: string;
|
||||
@Input() final: boolean = false;
|
||||
@Input() nestOdd: boolean;
|
||||
@Input() childFor: string;
|
||||
|
@ -25,11 +36,18 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
properties: any;
|
||||
_isArray: boolean;
|
||||
normalizer: SchemaNormalizer;
|
||||
autoExpand = false;
|
||||
descendants: any;
|
||||
|
||||
constructor(specMgr:SpecManager, private _renderer: Renderer, private _elementRef: ElementRef) {
|
||||
super(specMgr);
|
||||
// @ViewChildren(Zippy) childZippies: QueryList<Zippy>;
|
||||
// @ViewChildren(forwardRef(() => JsonSchemaLazy)) childSchemas: QueryList<JsonSchemaLazy>;
|
||||
|
||||
constructor(
|
||||
specMgr:SpecManager,
|
||||
app: AppStateService,
|
||||
private _renderer: Renderer,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private _elementRef: ElementRef) {
|
||||
super(specMgr, app);
|
||||
this.normalizer = new SchemaNormalizer(specMgr);
|
||||
}
|
||||
|
||||
|
@ -78,6 +96,8 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
|
||||
init() {
|
||||
if (!this.pointer) return;
|
||||
if (!this.absolutePointer) this.absolutePointer = this.pointer;
|
||||
|
||||
this.schema = this.componentSchema;
|
||||
if (!this.schema) {
|
||||
throw new Error(`Can't load component schema at ${this.pointer}`);
|
||||
|
@ -88,6 +108,7 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
this.schema = this.normalizer.normalize(this.schema, this.normPointer, {resolved: true});
|
||||
this.schema = SchemaHelper.unwrapArray(this.schema, this.normPointer);
|
||||
this._isArray = this.schema._isArray;
|
||||
this.absolutePointer += (this._isArray ? '/items' : '');
|
||||
this.initDescendants();
|
||||
this.preprocessSchema();
|
||||
}
|
||||
|
@ -114,7 +135,9 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
return (propSchema && propSchema.type === 'object' && propSchema._pointer);
|
||||
});
|
||||
|
||||
this.autoExpand = this.properties && this.properties.length === 1;
|
||||
if (this.properties.length === 1) {
|
||||
this.properties[0].expanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
applyStyling() {
|
||||
|
@ -127,6 +150,22 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
return item.name + (item._pointer || '');
|
||||
}
|
||||
|
||||
ensureSearchIsShown(ptr: string) {
|
||||
if (ptr.startsWith(this.absolutePointer)) {
|
||||
let props = this.properties;
|
||||
let relative = JsonPointer.relative(this.absolutePointer, ptr);
|
||||
let propName;
|
||||
if (relative.length > 1 && relative[0] === 'properties') {
|
||||
propName = relative[1];
|
||||
}
|
||||
let prop = props.find(p => p._name === propName);
|
||||
if (!prop) return;
|
||||
prop.expanded = true;
|
||||
this.cdr.markForCheck();
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import { BaseComponent } from '../base';
|
|||
import * as detectScollParent from 'scrollparent';
|
||||
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
import { OptionsService, Hash, AppStateService, SchemaHelper } from '../../services/index';
|
||||
import { SearchService, OptionsService, Hash, AppStateService, SchemaHelper } from '../../services/';
|
||||
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';
|
||||
|
||||
@Component({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<h2 class="responses-list-header" *ngIf="responses.length"> Responses </h2>
|
||||
<zippy *ngFor="let response of responses;trackBy:trackByCode" [title]="response.code + ' ' + response.description | marked"
|
||||
[type]="response.type" [visible]="response.expanded" [empty]="response.empty" (open)="lazySchema.load()">
|
||||
[type]="response.type" [(open)]="response.expanded" [empty]="response.empty" (openChange)="lazySchema.load()">
|
||||
<div *ngIf="response.headers" class="response-headers">
|
||||
<header>
|
||||
Headers
|
||||
|
@ -20,6 +20,7 @@
|
|||
<header *ngIf="response.schema">
|
||||
Response Schema
|
||||
</header>
|
||||
<json-schema-lazy [auto]="response.expanded" #lazySchema pointer="{{response.schema ? response.pointer + '/schema' : null}}">
|
||||
<json-schema-lazy [auto]="response.expanded" #lazySchema
|
||||
pointer="{{response.schema ? response.pointer + '/schema' : null}}">
|
||||
</json-schema-lazy>
|
||||
</zippy>
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
import { Component,
|
||||
Input,
|
||||
OnInit,
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
import { BaseSearchableComponent, SpecManager } from '../base';
|
||||
import JsonPointer from '../../utils/JsonPointer';
|
||||
import { statusCodeType } from '../../utils/helpers';
|
||||
import { OptionsService } from '../../services/index';
|
||||
import { OptionsService, AppStateService } from '../../services/index';
|
||||
import { SchemaHelper } from '../../services/schema-helper.service';
|
||||
|
||||
function isNumeric(n) {
|
||||
|
@ -17,14 +23,18 @@ function isNumeric(n) {
|
|||
styleUrls: ['./responses-list.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ResponsesList extends BaseComponent implements OnInit {
|
||||
export class ResponsesList extends BaseSearchableComponent implements OnInit {
|
||||
@Input() pointer:string;
|
||||
|
||||
responses: Array<any>;
|
||||
options: any;
|
||||
|
||||
constructor(specMgr:SpecManager, optionsMgr:OptionsService) {
|
||||
super(specMgr);
|
||||
constructor(specMgr:SpecManager,
|
||||
optionsMgr:OptionsService,
|
||||
app: AppStateService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {
|
||||
super(specMgr, app);
|
||||
this.options = optionsMgr.options;
|
||||
}
|
||||
|
||||
|
@ -50,6 +60,7 @@ export class ResponsesList extends BaseComponent implements OnInit {
|
|||
resp.code = respCode;
|
||||
resp.type = statusCodeType(resp.code);
|
||||
|
||||
resp.expanded = false;
|
||||
if (this.options.expandResponses) {
|
||||
if (this.options.expandResponses === 'all' || this.options.expandResponses.has(respCode.toString())) {
|
||||
resp.expanded = true;
|
||||
|
@ -74,6 +85,17 @@ export class ResponsesList extends BaseComponent implements OnInit {
|
|||
return el.code;
|
||||
}
|
||||
|
||||
ensureSearchIsShown(ptr: string) {
|
||||
if (ptr.startsWith(this.pointer)) {
|
||||
let code = JsonPointer.relative(this.pointer, ptr)[0];
|
||||
if (code && this.componentSchema[code]) {
|
||||
this.componentSchema[code].expanded = true;
|
||||
this.cdr.markForCheck();
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
<input #search (keyup)="update(search.value)">
|
||||
<button (click)="tmpSearch()"> Expand Search </button>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, HostBinding } from '@angular/core';
|
||||
import { Marker } from '../../services/';
|
||||
import { Marker, SearchService } from '../../services/';
|
||||
|
||||
@Component({
|
||||
selector: 'redoc-search',
|
||||
|
@ -11,7 +11,7 @@ import { Marker } from '../../services/';
|
|||
export class RedocSearch implements OnInit {
|
||||
logo:any = {};
|
||||
|
||||
constructor(private marker: Marker) {
|
||||
constructor(private marker: Marker, public search: SearchService) {
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -22,6 +22,14 @@ export class RedocSearch implements OnInit {
|
|||
this.marker.mark(val);
|
||||
}
|
||||
|
||||
tmpSearch() {
|
||||
this.search.ensureSearchVisible([
|
||||
'/paths/~1pet~1findByStatus/get/responses/200/schema/items/properties/category/properties/sub',
|
||||
'/paths/~1pet~1findByStatus/get/responses/200/schema/items/properties/tags',
|
||||
'/paths/~1pet/post/parameters/0/schema/properties/tags'
|
||||
]);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
'use strict';
|
||||
import { OnInit, OnDestroy } from '@angular/core';
|
||||
import { SpecManager } from '../utils/spec-manager';
|
||||
|
||||
import { AppStateService } from '../services/app-state.service';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
export { SpecManager };
|
||||
|
||||
|
@ -65,3 +66,35 @@ export class BaseComponent implements OnInit, OnDestroy {
|
|||
// emtpy
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseSearchableComponent extends BaseComponent {
|
||||
searchSubscription: Subscription;
|
||||
constructor(public specMgr: SpecManager, public app: AppStateService) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
subscribeForSearch() {
|
||||
this.searchSubscription = this.app.searchContainingPointers.subscribe(ptrs => {
|
||||
for (let i = 0; i < ptrs.length; ++i) {
|
||||
this.ensureSearchIsShown(ptrs[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
preinit() {
|
||||
super.preinit();
|
||||
this.subscribeForSearch();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.searchSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
+ Used to destroy component
|
||||
* @abstract
|
||||
*/
|
||||
ensureSearchIsShown(ptr: string) {
|
||||
// empy
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
ComponentParser,
|
||||
ContentProjector,
|
||||
Marker,
|
||||
SearchService,
|
||||
COMPONENT_PARSER_ALLOWED } from './services/';
|
||||
import { SpecManager } from './utils/spec-manager';
|
||||
|
||||
|
@ -35,6 +36,7 @@ import { SpecManager } from './utils/spec-manager';
|
|||
AppStateService,
|
||||
ComponentParser,
|
||||
ContentProjector,
|
||||
SearchService,
|
||||
LazyTasksService,
|
||||
Marker,
|
||||
{ provide: APP_ID, useValue: 'redoc' },
|
||||
|
|
|
@ -11,6 +11,8 @@ export class AppStateService {
|
|||
loading = new Subject<boolean>();
|
||||
initialized = new BehaviorSubject<any>(false);
|
||||
|
||||
searchContainingPointers = new BehaviorSubject<string[]>([]);
|
||||
|
||||
startLoading() {
|
||||
this.loading.next(true);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ export * from './hash.service';
|
|||
export * from './schema-normalizer.service';
|
||||
export * from './schema-helper.service';
|
||||
export * from './warnings.service';
|
||||
export * from './search.service';
|
||||
|
||||
export * from './component-parser.service';
|
||||
export * from './content-projector.service';
|
||||
|
|
13
lib/services/search.service.ts
Normal file
13
lib/services/search.service.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { AppStateService } from './app-state.service';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
constructor(private app: AppStateService) {
|
||||
window['locator'] = this;
|
||||
}
|
||||
ensureSearchVisible(containingPointers: string[]) {
|
||||
this.app.searchContainingPointers.next(containingPointers);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<div class="zippy zippy-{{type}}" [ngClass]="{'zippy-empty': empty, 'zippy-hidden': !visible}">
|
||||
<div class="zippy zippy-{{type}}" [ngClass]="{'zippy-empty': empty, 'zippy-hidden': !open}">
|
||||
<div *ngIf='!headless' class="zippy-title" (click)="toggle()">
|
||||
<span class="zippy-indicator">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
import { Component, EventEmitter, Output, Input } from '@angular/core';
|
||||
import { Component, EventEmitter, Output, Input, OnChanges } from '@angular/core';
|
||||
@Component({
|
||||
selector: 'zippy',
|
||||
templateUrl: './zippy.html',
|
||||
styleUrls: ['./zippy.css']
|
||||
})
|
||||
export class Zippy {
|
||||
export class Zippy implements OnChanges {
|
||||
@Input() type = 'general';
|
||||
@Input() visible = false;
|
||||
@Input() empty = false;
|
||||
@Input() title;
|
||||
@Input() headless: boolean = false;
|
||||
@Output() open = new EventEmitter();
|
||||
@Output() close = new EventEmitter();
|
||||
@Input() open = false;
|
||||
@Output() openChange = new EventEmitter();
|
||||
|
||||
|
||||
toggle() {
|
||||
this.visible = !this.visible;
|
||||
this.open = !this.open;
|
||||
if (this.empty) return;
|
||||
if (this.visible) {
|
||||
this.open.next({});
|
||||
} else {
|
||||
this.close.next({});
|
||||
this.openChange.emit(this.open);
|
||||
}
|
||||
|
||||
ngOnChanges(ch) {
|
||||
if (ch.open.currentValue === true) {
|
||||
this.openChange.emit(ch.open.currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,20 @@ export class JsonPointer {
|
|||
return JsonPointerLib.compile(tokens.slice(0, tokens.length - level));
|
||||
}
|
||||
|
||||
/**
|
||||
* returns relative path tokens
|
||||
* @example
|
||||
* // returns ['subpath']
|
||||
* JsonPointerHelper.relative('/path/0', '/path/0/subpath')
|
||||
* // returns ['foo', 'subpath']
|
||||
* JsonPointerHelper.relative('/path', '/path/foo/subpath')
|
||||
*/
|
||||
static relative(from, to):string[] {
|
||||
let fromTokens = JsonPointer.parse(from);
|
||||
let toTokens = JsonPointer.parse(to);
|
||||
return toTokens.slice(fromTokens.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* overridden JsonPointer original parse to take care of prefixing '#' symbol
|
||||
* that is not valid JsonPointer
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
export * from './custom-error-handler';
|
||||
export * from './helpers';
|
||||
export * from './md-renderer';
|
||||
|
||||
export { default as JsonPointer } from './JsonPointer';
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
"deploy": "node ./build/prepare_deploy.js && deploy-to-gh-pages --update demo",
|
||||
"ngc": "ngc -p .",
|
||||
"clean:dist": "npm run rimraf -- dist/",
|
||||
"clean:aot": "npm run rimraf -- .tmp lib/**/*.css",
|
||||
"clean:aot": "npm run rimraf -- .tmp compiled lib/**/*.css",
|
||||
"rimraf": "rimraf",
|
||||
"webpack:prod": "webpack --config build/webpack.prod.js --profile --bail",
|
||||
"build:sass": "node-sass -q -o lib lib",
|
||||
|
|
Loading…
Reference in New Issue
Block a user