mirror of
				https://github.com/Redocly/redoc.git
				synced 2025-11-04 01:37:32 +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