mirror of
https://github.com/Redocly/redoc.git
synced 2025-01-31 18:14:07 +03:00
Basic Indexing + search
This commit is contained in:
parent
21a3ab0b1f
commit
e1ce5ea316
|
@ -159,7 +159,7 @@ export class JsonSchema extends BaseSearchableComponent implements OnInit {
|
||||||
propName = relative[1];
|
propName = relative[1];
|
||||||
}
|
}
|
||||||
let prop = props.find(p => p._name === propName);
|
let prop = props.find(p => p._name === propName);
|
||||||
if (!prop) return;
|
if (!prop || prop.isTrivial) return;
|
||||||
prop.expanded = true;
|
prop.expanded = true;
|
||||||
this.cdr.markForCheck();
|
this.cdr.markForCheck();
|
||||||
this.cdr.detectChanges();
|
this.cdr.detectChanges();
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
<input #search (keyup)="update(search.value)">
|
<input #search (keyup)="update(search.value)" placeholder="Search">
|
||||||
<button (click)="tmpSearch()"> Expand Search </button>
|
<ul class="search-results">
|
||||||
|
<li class="result" *ngFor="let item of items" (click)="clickSearch(item)">
|
||||||
|
{{item.menuItem.name}}
|
||||||
|
<li>
|
||||||
|
</ul>
|
||||||
|
|
|
@ -1,3 +1,22 @@
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { Component, ChangeDetectionStrategy, OnInit, HostBinding } from '@angular/core';
|
import { Component, ChangeDetectionStrategy, OnInit, HostBinding } from '@angular/core';
|
||||||
import { Marker, SearchService } from '../../services/';
|
import { Marker, SearchService, MenuService } from '../../services/';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redoc-search',
|
selector: 'redoc-search',
|
||||||
|
@ -10,27 +10,34 @@ import { Marker, SearchService } from '../../services/';
|
||||||
})
|
})
|
||||||
export class RedocSearch implements OnInit {
|
export class RedocSearch implements OnInit {
|
||||||
logo:any = {};
|
logo:any = {};
|
||||||
|
items: any[] = [];
|
||||||
|
|
||||||
constructor(private marker: Marker, public search: SearchService) {
|
constructor(private marker: Marker, public search: SearchService, public menu: MenuService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
this.search.indexAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
update(val) {
|
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);
|
this.marker.mark(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpSearch() {
|
clickSearch(item) {
|
||||||
this.search.ensureSearchVisible([
|
this.search.ensureSearchVisible(
|
||||||
'/paths/~1pet~1findByStatus/get/responses/200/schema/items/properties/category/properties/sub',
|
item.pointers
|
||||||
'/paths/~1pet~1findByStatus/get/responses/200/schema/items/properties/tags',
|
);
|
||||||
'/paths/~1pet/post/parameters/0/schema/properties/tags'
|
this.marker.remark();
|
||||||
]);
|
this.menu.activate(item.menuItem.flatIdx);
|
||||||
|
this.menu.scrollToActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -417,6 +417,10 @@ export class MenuService {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getItemById(id: string):MenuItem {
|
||||||
|
return this.flatItems.find(item => item.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this._hashSubscription.unsubscribe();
|
this._hashSubscription.unsubscribe();
|
||||||
this._scrollSubscription.unsubscribe();
|
this._scrollSubscription.unsubscribe();
|
||||||
|
|
|
@ -1,13 +1,124 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { AppStateService } from './app-state.service';
|
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<IndexElement> = {};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchService {
|
export class SearchService {
|
||||||
constructor(private app: AppStateService) {
|
constructor(private app: AppStateService, private spec: SpecManager) {
|
||||||
window['locator'] = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureSearchVisible(containingPointers: string[]) {
|
ensureSearchVisible(containingPointers: string[]) {
|
||||||
this.app.searchContainingPointers.next(containingPointers);
|
this.app.searchContainingPointers.next(containingPointers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
indexAll() {
|
||||||
|
const swagger = this.spec.schema;
|
||||||
|
|
||||||
|
this.indexPaths(swagger);
|
||||||
|
}
|
||||||
|
|
||||||
|
search(q):StringMap<IndexElement[]> {
|
||||||
|
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);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
export interface StringMap<T> {
|
||||||
|
[key: string]: T;
|
||||||
|
}
|
||||||
|
|
||||||
export function stringify(obj:any) {
|
export function stringify(obj:any) {
|
||||||
return JSON.stringify(obj);
|
return JSON.stringify(obj);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +20,18 @@ export function isBlank(obj: any): boolean {
|
||||||
return obj == undefined;
|
return obj == undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
export function groupBy<T>(array: T[], key:string):StringMap<T[]> {
|
||||||
|
return array.reduce<StringMap<T[]>>(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) {
|
export function statusCodeType(statusCode) {
|
||||||
if (statusCode < 100 || statusCode > 599) {
|
if (statusCode < 100 || statusCode > 599) {
|
||||||
throw new Error('invalid HTTP code');
|
throw new Error('invalid HTTP code');
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export * from './custom-error-handler';
|
export * from './custom-error-handler';
|
||||||
export * from './helpers';
|
export * from './helpers';
|
||||||
export * from './md-renderer';
|
export * from './md-renderer';
|
||||||
|
export * from './spec-manager';
|
||||||
|
|
||||||
export { default as JsonPointer } from './JsonPointer';
|
export { default as JsonPointer } from './JsonPointer';
|
||||||
|
|
|
@ -111,6 +111,7 @@
|
||||||
"hint.css": "^2.3.2",
|
"hint.css": "^2.3.2",
|
||||||
"json-pointer": "^0.6.0",
|
"json-pointer": "^0.6.0",
|
||||||
"json-schema-ref-parser": "^3.1.2",
|
"json-schema-ref-parser": "^3.1.2",
|
||||||
|
"lunr": "^0.7.2",
|
||||||
"mark.js": "github:julmot/mark.js",
|
"mark.js": "github:julmot/mark.js",
|
||||||
"openapi-sampler": "^0.3.3",
|
"openapi-sampler": "^0.3.3",
|
||||||
"prismjs": "^1.5.1",
|
"prismjs": "^1.5.1",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user