diff --git a/lib/components/JsonSchema/json-schema.ts b/lib/components/JsonSchema/json-schema.ts
index 9e2058a4..d7d41493 100644
--- a/lib/components/JsonSchema/json-schema.ts
+++ b/lib/components/JsonSchema/json-schema.ts
@@ -159,7 +159,7 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit {
propName = relative[1];
}
let prop = props.find(p => p._name === propName);
- if (!prop) return;
+ if (!prop || prop.isTrivial) return;
prop.expanded = true;
this.cdr.markForCheck();
this.cdr.detectChanges();
diff --git a/lib/components/Search/redoc-search.html b/lib/components/Search/redoc-search.html
index 598afffc..43ee86d9 100644
--- a/lib/components/Search/redoc-search.html
+++ b/lib/components/Search/redoc-search.html
@@ -1,2 +1,6 @@
-
-
+
+
+ -
+ {{item.menuItem.name}}
+
-
+
diff --git a/lib/components/Search/redoc-search.scss b/lib/components/Search/redoc-search.scss
index 5d4e87f3..0d2c890a 100644
--- a/lib/components/Search/redoc-search.scss
+++ b/lib/components/Search/redoc-search.scss
@@ -1,3 +1,22 @@
:host {
display: block;
+ padding: 20px;
+ background: silver;
+}
+
+input {
+ width: 100%;
+ box-sizing: border-box;
+ padding: 5px;
+}
+
+.search-results {
+ margin: 10px 0 0;
+ padding: 0;
+ list-style: none;
+
+ > li {
+ display: block;
+ cursor: pointer;
+ }
}
diff --git a/lib/components/Search/redoc-search.ts b/lib/components/Search/redoc-search.ts
index 00db55f8..6f38f4e7 100644
--- a/lib/components/Search/redoc-search.ts
+++ b/lib/components/Search/redoc-search.ts
@@ -1,6 +1,6 @@
'use strict';
import { Component, ChangeDetectionStrategy, OnInit, HostBinding } from '@angular/core';
-import { Marker, SearchService } from '../../services/';
+import { Marker, SearchService, MenuService } from '../../services/';
@Component({
selector: 'redoc-search',
@@ -10,27 +10,34 @@ import { Marker, SearchService } from '../../services/';
})
export class RedocSearch implements OnInit {
logo:any = {};
+ items: any[] = [];
- constructor(private marker: Marker, public search: SearchService) {
+ constructor(private marker: Marker, public search: SearchService, public menu: MenuService) {
}
init() {
-
+ this.search.indexAll();
}
update(val) {
+ let searchRes = this.search.search(val);
+ this.items = Object.keys(searchRes).map(id => ({
+ menuItem: this.menu.getItemById(id),
+ pointers: searchRes[id].map(el => el.pointer)
+ }));
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'
- ]);
+ clickSearch(item) {
+ this.search.ensureSearchVisible(
+ item.pointers
+ );
+ this.marker.remark();
+ this.menu.activate(item.menuItem.flatIdx);
+ this.menu.scrollToActive();
}
ngOnInit() {
-
+ this.init();
}
}
diff --git a/lib/services/menu.service.ts b/lib/services/menu.service.ts
index ab959a6f..5d6ad8b0 100644
--- a/lib/services/menu.service.ts
+++ b/lib/services/menu.service.ts
@@ -417,6 +417,10 @@ export class MenuService {
return res;
}
+ getItemById(id: string):MenuItem {
+ return this.flatItems.find(item => item.id === id);
+ }
+
destroy() {
this._hashSubscription.unsubscribe();
this._scrollSubscription.unsubscribe();
diff --git a/lib/services/search.service.ts b/lib/services/search.service.ts
index 15813a89..f16f72c3 100644
--- a/lib/services/search.service.ts
+++ b/lib/services/search.service.ts
@@ -1,13 +1,124 @@
import { Injectable } from '@angular/core';
import { AppStateService } from './app-state.service';
+import { JsonPointer, groupBy, SpecManager, StringMap} from '../utils/';
+import * as lunr from 'lunr';
+
+interface IndexElement {
+ menuPtr: string;
+ title: string;
+ body: string;
+ pointer: string;
+}
+
+const index = lunr(function () {
+ this.field('menuPtr', {boost: 0});
+ this.field('title', {boost: 1.5});
+ this.field('body');
+ this.ref('pointer');
+})
+
+const store:StringMap = {};
@Injectable()
export class SearchService {
- constructor(private app: AppStateService) {
- window['locator'] = this;
+ constructor(private app: AppStateService, private spec: SpecManager) {
}
+
ensureSearchVisible(containingPointers: string[]) {
this.app.searchContainingPointers.next(containingPointers);
}
+
+ indexAll() {
+ const swagger = this.spec.schema;
+
+ this.indexPaths(swagger);
+ }
+
+ search(q):StringMap {
+ const res:IndexElement[] = index.search(q).map(res => {
+ return store[res.ref];
+ });
+ const grouped = groupBy(res, 'menuPtr');
+ return grouped;
+ }
+
+ index(element: IndexElement) {
+ index.add(element);
+ store[element.pointer] = element;
+ }
+
+ indexPaths(swagger:any) {
+ const paths = swagger.paths
+ const basePtr = '#/paths';
+ Object.keys(paths).forEach(path => {
+ let opearations = paths[path];
+ Object.keys(opearations).forEach(verb => {
+ const opearation = opearations[verb];
+ const ptr = JsonPointer.join(basePtr, [path, verb]);
+ this.indexOperation(opearation, ptr);
+ });
+ });
+ }
+
+ indexOperation(operation:any, opPtr:string) {
+ this.index({
+ pointer: opPtr,
+ menuPtr: opPtr,
+ title: operation.summary,
+ body: operation.description
+ });
+ this.indexOperationResponses(operation, opPtr);
+ }
+
+ indexOperationResponses(operation:any, operationPtr:string) {
+ const responses = operation.responses;
+ if (!responses) return;
+ Object.keys(responses).forEach(code => {
+ const resp = responses[code];
+ const respPtr = JsonPointer.join(operationPtr, ['responses', code]);
+ this.index({
+ pointer: respPtr,
+ menuPtr: operationPtr,
+ title: code,
+ body: resp.description
+ });
+
+ if (resp.schema) {
+ this.indexSchema(resp.schema, '', JsonPointer.join(respPtr, 'schema'), operationPtr);
+ }
+ });
+ }
+
+ indexSchema(_schema:any, name: string, absolutePointer: string, menuPointer: string) {
+ if (!_schema) return;
+ let schema = _schema;
+ let title = name;
+ if (schema.$ref) {
+ schema = this.spec.byPointer(_schema.$ref);
+ title = name + ' ' + JsonPointer.baseName(_schema.$ref);
+ }
+
+ let body = schema.description; // TODO: defaults, examples, etc...
+
+ if (schema.type === 'array') {
+ this.indexSchema(schema.items, title, JsonPointer.join(absolutePointer, ['items']), menuPointer);
+ return;
+ }
+
+ this.index({
+ pointer: absolutePointer,
+ menuPtr: menuPointer,
+ title,
+ body
+ })
+
+ // TODO: allof etc
+ if (schema.properties) {
+ Object.keys(schema.properties).forEach(propName => {
+ let propPtr = JsonPointer.join(absolutePointer, ['properties', propName]);
+ this.indexSchema(schema.properties[propName], propName, propPtr, menuPointer);
+ })
+ }
+ }
}
diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts
index 4366388e..00cf4fd7 100644
--- a/lib/utils/helpers.ts
+++ b/lib/utils/helpers.ts
@@ -1,5 +1,9 @@
'use strict';
+export interface StringMap {
+ [key: string]: T;
+}
+
export function stringify(obj:any) {
return JSON.stringify(obj);
}
@@ -16,6 +20,18 @@ export function isBlank(obj: any): boolean {
return obj == undefined;
}
+const hasOwnProperty = Object.prototype.hasOwnProperty;
+export function groupBy(array: T[], key:string):StringMap {
+ return array.reduce>(function(res, value) {
+ if (hasOwnProperty.call(res, value[key])) {
+ res[value[key]].push(value);
+ } else {
+ res[value[key]] = [value];
+ }
+ return res;
+ }, {});
+}
+
export function statusCodeType(statusCode) {
if (statusCode < 100 || statusCode > 599) {
throw new Error('invalid HTTP code');
diff --git a/lib/utils/index.ts b/lib/utils/index.ts
index 79d57372..06c03491 100644
--- a/lib/utils/index.ts
+++ b/lib/utils/index.ts
@@ -1,5 +1,6 @@
export * from './custom-error-handler';
export * from './helpers';
export * from './md-renderer';
+export * from './spec-manager';
export { default as JsonPointer } from './JsonPointer';
diff --git a/package.json b/package.json
index dd0c9b3d..477b0e76 100644
--- a/package.json
+++ b/package.json
@@ -111,6 +111,7 @@
"hint.css": "^2.3.2",
"json-pointer": "^0.6.0",
"json-schema-ref-parser": "^3.1.2",
+ "lunr": "^0.7.2",
"mark.js": "github:julmot/mark.js",
"openapi-sampler": "^0.3.3",
"prismjs": "^1.5.1",