Use operationId in links; fixes #14

This commit is contained in:
Roman Hotsiy 2016-02-07 16:10:32 +02:00
parent 7f57558225
commit fa81187e10
7 changed files with 66 additions and 16 deletions

View File

@ -1,7 +1,7 @@
<div class="method"> <div class="method">
<div class="method-content"> <div class="method-content">
<h2 class="method-header sharable-header"> <h2 class="method-header sharable-header">
<a class="share-link" href="#{{tag}}{{pointer}}"></a>{{data.methodInfo.summary}} <a class="share-link" href="#{{data.methodAnchor}}"></a>{{data.methodInfo.summary}}
</h2> </h2>
<h3 class="method-endpoint"> <h3 class="method-endpoint">
<span class="http-method" [ngClass]="data.httpMethod">{{data.httpMethod}}</span> <span class="http-method" [ngClass]="data.httpMethod">{{data.httpMethod}}</span>

View File

@ -28,6 +28,11 @@ export default class Method extends BaseComponent {
this.data.methodInfo = this.componentSchema; this.data.methodInfo = this.componentSchema;
this.data.methodInfo.tags = this.filterMainTags(this.data.methodInfo.tags); this.data.methodInfo.tags = this.filterMainTags(this.data.methodInfo.tags);
this.data.bodyParam = this.findBodyParam(); this.data.bodyParam = this.findBodyParam();
if (this.componentSchema.operationId) {
this.data.methodAnchor = 'operation/' + this.componentSchema.operationId;
} else {
this.data.methodAnchor = 'tag/' + this.tag + this.pointer;
}
} }
filterMainTags(tags) { filterMainTags(tags) {

View File

@ -1,10 +1,10 @@
<div class="methods"> <div class="methods">
<div class="tag" *ngFor="#tag of data.tags"> <div class="tag" *ngFor="#tag of data.tags">
<div class="tag-info" [attr.tag]="tag.name"> <div class="tag-info" [attr.tag]="tag.name">
<h1 class="sharable-header"> <a class="share-link" href="#{{tag.name}}"></a>{{tag.name}} </h1> <h1 class="sharable-header"> <a class="share-link" href="#tag/{{tag.name}}"></a>{{tag.name}} </h1>
<p *ngIf="tag.description" innerHtml="{{ tag.description | marked }}"> </p> <p *ngIf="tag.description" innerHtml="{{ tag.description | marked }}"> </p>
</div> </div>
<method *ngFor="#method of tag.methods" [pointer]="method.pointer" [attr.pointer]="method.pointer" <method *ngFor="#method of tag.methods" [pointer]="method.pointer" [attr.pointer]="method.pointer"
[attr.tag]="method.tag" [tag]="method.tag"></method> [attr.tag]="method.tag" [tag]="method.tag" [attr.operation-id]="method.operationId"></method>
</div> </div>
</div> </div>

View File

@ -3,7 +3,8 @@
import {RedocComponent, BaseComponent} from '../base'; import {RedocComponent, BaseComponent} from '../base';
import {redocEvents} from '../../events'; import {redocEvents} from '../../events';
import {NgZone, ChangeDetectionStrategy, ElementRef} from 'angular2/core'; import {NgZone, ChangeDetectionStrategy, ElementRef, forwardRef} from 'angular2/core';
import Redoc from '../Redoc/redoc';
import {document} from 'angular2/src/facade/browser'; import {document} from 'angular2/src/facade/browser';
import {BrowserDomAdapter} from 'angular2/platform/browser'; import {BrowserDomAdapter} from 'angular2/platform/browser';
import {global} from 'angular2/src/facade/lang'; import {global} from 'angular2/src/facade/lang';
@ -60,10 +61,18 @@ export default class SideMenu extends BaseComponent {
let hash = this.adapter.getLocation().hash; let hash = this.adapter.getLocation().hash;
if (!hash) return; if (!hash) return;
let el;
hash = hash.substr(1); hash = hash.substr(1);
let tag = hash.split('/')[0]; let namespace = hash.split('/')[0];
let ptr = hash.substr(tag.length); let ptr = hash.substr(namespace.length + 1);
let el = this.getMethodEl(ptr, tag); if (namespace === 'operation') {
el = this.getMethodElByOperId(ptr);
} else if (namespace === 'tag') {
let tag = ptr.split('/')[0];
ptr = ptr.substr(tag.length);
el = this.getMethodElByPtr(ptr, tag);
}
if (el) this.scrollTo(el); if (el) this.scrollTo(el);
if (evt) evt.preventDefault(); if (evt) evt.preventDefault();
} }
@ -170,13 +179,18 @@ export default class SideMenu extends BaseComponent {
return (methodIdx === 0 && catIdx === 0); return (methodIdx === 0 && catIdx === 0);
} }
getMethodEl(ptr, tag) { getMethodElByPtr(ptr, tag) {
let selector = ptr ? `[pointer="${ptr}"][tag="${tag}"]` : `[tag="${tag}"]`; let selector = ptr ? `[pointer="${ptr}"][tag="${tag}"]` : `[tag="${tag}"]`;
return document.querySelector(selector); return document.querySelector(selector);
} }
getMethodElByOperId(operationId) {
let selector =`[operation-id="${operationId}"]`;
return document.querySelector(selector);
}
getCurrentMethodEl() { getCurrentMethodEl() {
return this.getMethodEl(this.activeMethodPtr, this.data.menu[this.activeCatIdx].name); return this.getMethodElByPtr(this.activeMethodPtr, this.data.menu[this.activeCatIdx].name);
} }
/* returns 1 if element if above the view, 0 if in view and -1 below the view */ /* returns 1 if element if above the view, 0 if in view and -1 below the view */
@ -240,4 +254,5 @@ export default class SideMenu extends BaseComponent {
this.changeActive(CHANGE.INITIAL); this.changeActive(CHANGE.INITIAL);
} }
} }
SideMenu.parameters = SideMenu.parameters.concat([[ElementRef], [BrowserDomAdapter], [NgZone]]); SideMenu.parameters = SideMenu.parameters.concat([[ElementRef],
[BrowserDomAdapter], [NgZone], [forwardRef(() => Redoc)] ]);

View File

@ -75,8 +75,22 @@ describe('Redoc components', () => {
}); });
}); });
it('should scroll to method when location hash is present', (done) => { it('should scroll to method when location hash is present [jp]', (done) => {
let hash = '#pet/paths/~1pet~1findByStatus/get'; let hash = '#tag/pet/paths/~1pet~1findByStatus/get';
spyOn(component.adapter, 'getLocation').and.returnValue({hash: hash});
spyOn(component, 'hashScroll').and.callThrough();
spyOn(window, 'scrollTo').and.stub();
redocEvents.bootstrapped.next();
setTimeout(() => {
expect(component.hashScroll).toHaveBeenCalled();
let scrollY = window.scrollTo.calls.argsFor(0)[1];
expect(scrollY).toBeGreaterThan(0);
done();
});
});
it('should scroll to method when location hash is present [operation]', (done) => {
let hash = '#operation/getPetById';
spyOn(component.adapter, 'getLocation').and.returnValue({hash: hash}); spyOn(component.adapter, 'getLocation').and.returnValue({hash: hash});
spyOn(component, 'hashScroll').and.callThrough(); spyOn(component, 'hashScroll').and.callThrough();
spyOn(window, 'scrollTo').and.stub(); spyOn(window, 'scrollTo').and.stub();
@ -121,8 +135,20 @@ describe('Redoc components', () => {
expect(component.data).not.toBeNull(); expect(component.data).not.toBeNull();
}); });
it('should scroll to method when location hash is present', (done) => { it('should scroll to method when location hash is present [jp]', (done) => {
let hash = '#pet/paths/~1pet~1findByStatus/get'; let hash = '#tag/pet/paths/~1pet~1findByStatus/get';
spyOn(component.adapter, 'getLocation').and.returnValue({hash: hash});
spyOn(component, 'hashScroll').and.callThrough();
redocEvents.bootstrapped.next();
setTimeout(() => {
expect(component.hashScroll).toHaveBeenCalled();
expect(component.scrollParent.scrollTop).toBeGreaterThan(0);
done();
});
});
it('should scroll to method when location hash is present [operation]', (done) => {
let hash = '#operation/getPetById';
spyOn(component.adapter, 'getLocation').and.returnValue({hash: hash}); spyOn(component.adapter, 'getLocation').and.returnValue({hash: hash});
spyOn(component, 'hashScroll').and.callThrough(); spyOn(component, 'hashScroll').and.callThrough();
redocEvents.bootstrapped.next(); redocEvents.bootstrapped.next();

View File

@ -150,7 +150,11 @@ export default class SchemaManager {
} }
if (tagDetails['x-traitTag']) continue; if (tagDetails['x-traitTag']) continue;
if (!tagDetails.methods) tagDetails.methods = []; if (!tagDetails.methods) tagDetails.methods = [];
tagDetails.methods.push({pointer: methodPointer, summary: methodSummary}); tagDetails.methods.push({
pointer: methodPointer,
summary: methodSummary,
operationId: methodInfo.operationId
});
} }
} }
} }

View File

@ -165,7 +165,7 @@ describe('Utils', () => {
info.should.be.an.Object(); info.should.be.an.Object();
info.methods.should.be.an.Array(); info.methods.should.be.an.Array();
for (let methodInfo of info.methods) { for (let methodInfo of info.methods) {
methodInfo.should.have.keys('pointer', 'summary'); methodInfo.should.have.properties(['pointer', 'summary']);
let methSchema = schemaMgr.byPointer(methodInfo.pointer); let methSchema = schemaMgr.byPointer(methodInfo.pointer);
expect(methSchema).not.toBeNull(); expect(methSchema).not.toBeNull();
if (methSchema.summary) { if (methSchema.summary) {