From 46f6b29547f36d6098cdada0af4c01f6f175888b Mon Sep 17 00:00:00 2001 From: Roman Hotsiy Date: Thu, 29 Dec 2016 19:20:29 +0200 Subject: [PATCH] Lazy search highlight + basic search --- lib/components/ApiInfo/api-info.ts | 11 ++- lib/components/Redoc/redoc.html | 1 + lib/components/Search/redoc-search.html | 1 + lib/components/Search/redoc-search.scss | 3 + lib/components/Search/redoc-search.ts | 28 ++++++++ lib/components/SideMenu/side-menu.ts | 10 ++- lib/components/index.ts | 5 +- lib/redoc.module.ts | 2 + lib/services/index.ts | 1 + lib/services/marker.service.ts | 90 +++++++++++++++++++++++++ lib/services/menu.service.ts | 16 ++++- manual-types/index.d.ts | 1 + package.json | 1 + 13 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 lib/components/Search/redoc-search.html create mode 100644 lib/components/Search/redoc-search.scss create mode 100644 lib/components/Search/redoc-search.ts create mode 100644 lib/services/marker.service.ts diff --git a/lib/components/ApiInfo/api-info.ts b/lib/components/ApiInfo/api-info.ts index fcb8163c..2ea4f634 100644 --- a/lib/components/ApiInfo/api-info.ts +++ b/lib/components/ApiInfo/api-info.ts @@ -1,7 +1,7 @@ 'use strict'; -import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; +import { Component, ChangeDetectionStrategy, OnInit, ElementRef } from '@angular/core'; import { SpecManager, BaseComponent } from '../base'; -import { OptionsService } from '../../services/index'; +import { OptionsService, Marker } from '../../services/index'; @Component({ selector: 'api-info', @@ -12,8 +12,13 @@ import { OptionsService } from '../../services/index'; export class ApiInfo extends BaseComponent implements OnInit { info: any = {}; specUrl: String; - constructor(specMgr: SpecManager, private optionsService: OptionsService) { + constructor(specMgr: SpecManager, + private optionsService: OptionsService, + elRef: ElementRef, + marker: Marker + ) { super(specMgr); + marker.addElement(elRef.nativeElement); } init() { diff --git a/lib/components/Redoc/redoc.html b/lib/components/Redoc/redoc.html index a688d51a..15f48a01 100644 --- a/lib/components/Redoc/redoc.html +++ b/lib/components/Redoc/redoc.html @@ -9,6 +9,7 @@
diff --git a/lib/components/Search/redoc-search.html b/lib/components/Search/redoc-search.html new file mode 100644 index 00000000..c74bdbf3 --- /dev/null +++ b/lib/components/Search/redoc-search.html @@ -0,0 +1 @@ + diff --git a/lib/components/Search/redoc-search.scss b/lib/components/Search/redoc-search.scss new file mode 100644 index 00000000..5d4e87f3 --- /dev/null +++ b/lib/components/Search/redoc-search.scss @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/lib/components/Search/redoc-search.ts b/lib/components/Search/redoc-search.ts new file mode 100644 index 00000000..18f21b28 --- /dev/null +++ b/lib/components/Search/redoc-search.ts @@ -0,0 +1,28 @@ +'use strict'; +import { Component, ChangeDetectionStrategy, OnInit, HostBinding } from '@angular/core'; +import { Marker } from '../../services/'; + +@Component({ + selector: 'redoc-search', + styleUrls: ['./redoc-search.css'], + templateUrl: './redoc-search.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class RedocSearch implements OnInit { + logo:any = {}; + + constructor(private marker: Marker) { + } + + init() { + + } + + update(val) { + this.marker.mark(val); + } + + ngOnInit() { + + } +} diff --git a/lib/components/SideMenu/side-menu.ts b/lib/components/SideMenu/side-menu.ts index 27d30c1c..fbe3d7e7 100644 --- a/lib/components/SideMenu/side-menu.ts +++ b/lib/components/SideMenu/side-menu.ts @@ -5,7 +5,7 @@ import { Component, EventEmitter, Input, Output, ElementRef, ChangeDetectorRef, //import { global } from '@angular/core/src/facade/lang'; import { trigger, state, animate, transition, style } from '@angular/core'; import { BaseComponent, SpecManager } from '../base'; -import { ScrollService, MenuService, OptionsService, MenuItem } from '../../services/'; +import { ScrollService, MenuService, OptionsService, MenuItem, Marker} from '../../services/'; import { BrowserDomAdapter as DOM } from '../../utils/browser-adapter'; const global = window; @@ -55,7 +55,7 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy { constructor(specMgr:SpecManager, elementRef:ElementRef, private scrollService:ScrollService, private menuService:MenuService, - optionsService:OptionsService, private detectorRef:ChangeDetectorRef) { + optionsService:OptionsService, private detectorRef:ChangeDetectorRef, private marker:Marker) { super(specMgr); this.$element = elementRef.nativeElement; @@ -64,7 +64,8 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy { this.options = optionsService.options; - this.menuService.changed.subscribe((evt) => this.changed(evt)); + this.menuService.changedActiveItem.subscribe((evt) => this.changed(evt)); + this.menuService.changed.subscribe((evt) => this.detectorRef.detectChanges()); } changed(item) { @@ -147,4 +148,7 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy { ngOnInit() { this.preinit(); } + + ngAfterViewInit() { + } } diff --git a/lib/components/index.ts b/lib/components/index.ts index 4b4ffc32..ecf74a79 100644 --- a/lib/components/index.ts +++ b/lib/components/index.ts @@ -15,15 +15,16 @@ import { Method } from './Method/method'; import { Warnings } from './Warnings/warnings'; import { SecurityDefinitions } from './SecurityDefinitions/security-definitions'; import { LoadingBar } from './LoadingBar/loading-bar'; +import { RedocSearch } from './Search/redoc-search'; import { Redoc } from './Redoc/redoc'; export const REDOC_DIRECTIVES = [ ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList, ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions, - LoadingBar, SideMenuItems + LoadingBar, SideMenuItems, RedocSearch ]; export { ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList, ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions, -LoadingBar, SideMenuItems } +LoadingBar, SideMenuItems, RedocSearch } diff --git a/lib/redoc.module.ts b/lib/redoc.module.ts index b13b2dc3..4ba10894 100644 --- a/lib/redoc.module.ts +++ b/lib/redoc.module.ts @@ -16,6 +16,7 @@ import { AppStateService, ComponentParser, ContentProjector, + Marker, COMPONENT_PARSER_ALLOWED } from './services/'; import { SpecManager } from './utils/spec-manager'; @@ -35,6 +36,7 @@ import { SpecManager } from './utils/spec-manager'; ComponentParser, ContentProjector, LazyTasksService, + Marker, { provide: APP_ID, useValue: 'redoc' }, { provide: ErrorHandler, useClass: CustomErrorHandler }, { provide: COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': SecurityDefinitions} } diff --git a/lib/services/index.ts b/lib/services/index.ts index 8a33b956..7200d85a 100644 --- a/lib/services/index.ts +++ b/lib/services/index.ts @@ -11,3 +11,4 @@ export * from './warnings.service'; export * from './component-parser.service'; export * from './content-projector.service'; +export * from './marker.service'; diff --git a/lib/services/marker.service.ts b/lib/services/marker.service.ts new file mode 100644 index 00000000..bc42a725 --- /dev/null +++ b/lib/services/marker.service.ts @@ -0,0 +1,90 @@ +import { Injectable } from '@angular/core'; +import * as Mark from 'mark.js'; +import { MenuService } from './menu.service'; + +const ROLL_LEN = 5; +@Injectable() +export class Marker { + permInstances = []; + rolledInstances = new Array(ROLL_LEN); + term: string; + + currIdx = -1; + + constructor(private menu: MenuService) { + menu.changedActiveItem.subscribe(() => { + this.roll(); + }); + } + + addElement(el: Element) { + this.permInstances.push(new Mark(el)); + } + + newMarkerAtMenuItem(idx:number) { + let context = this.menu.getEl(idx); + + if (this.menu.isTagItem(idx)) { + context = this.menu.getTagInfoEl(idx); + } + let newInst = context && new Mark(context); + if (newInst && this.term) { + newInst.mark(this.term); + } + return newInst; + } + + roll() { + let newIdx = this.menu.activeIdx; + let diff = newIdx - this.currIdx; + this.currIdx = newIdx; + if (diff < 0) { + diff = - diff; + for (let i=0; i < Math.min(diff, ROLL_LEN); i++) { + let prevInst = this.rolledInstances.pop(); + if(prevInst) prevInst.unmark(); + + let idx = newIdx - Math.floor(ROLL_LEN/2) + i; + let newMark = this.newMarkerAtMenuItem(idx); + this.rolledInstances.unshift(newMark); + } + } else { + for (let i=0; i < Math.min(diff, ROLL_LEN); i++) { + let oldInst = this.rolledInstances.shift(); + oldInst && oldInst.unmark(); + + let idx = newIdx + Math.floor(ROLL_LEN/2) - i; + let newMark = this.newMarkerAtMenuItem(idx); + this.rolledInstances.push(newMark); + } + } + } + + mark(term: string) { + this.term = term || null; + this.remark(); + } + + remark() { + for (let marker of this.permInstances) { + if (marker) { + marker.unmark(); + if (this.term) marker.mark(this.term); + } + } + for (let marker of this.rolledInstances) { + if (marker) { + marker.unmark(); + if (this.term) marker.mark(this.term); + } + } + } + + unmark() { + this.term = null; + this.remark(); + } + + updateMark() { + } +} diff --git a/lib/services/menu.service.ts b/lib/services/menu.service.ts index ab0a2602..cdc8f64f 100644 --- a/lib/services/menu.service.ts +++ b/lib/services/menu.service.ts @@ -44,6 +44,7 @@ export interface MenuItem { @Injectable() export class MenuService { changed: EventEmitter = new EventEmitter(); + changedActiveItem: EventEmitter = new EventEmitter(); items: MenuItem[]; activeIdx: number = -1; @@ -135,6 +136,7 @@ export class MenuService { getEl(flatIdx:number):Element { if (flatIdx < 0) return null; + if (flatIdx > this.flatItems.length - 1) return null; let currentItem = this.flatItems[flatIdx]; if (!currentItem) return; if (currentItem.isGroup) currentItem = this.flatItems[flatIdx + 1]; @@ -156,6 +158,18 @@ export class MenuService { return selector ? document.querySelector(selector) : null; } + isTagItem(flatIdx: number):boolean { + let item = this.flatItems[flatIdx]; + return item && item.metadata && item.metadata.type === 'tag'; + } + + getTagInfoEl(flatIdx: number):Element { + if (!this.isTagItem(flatIdx)) return null; + + let el = this.getEl(flatIdx); + return el && el.querySelector('.tag-info'); + } + getCurrentEl():Element { return this.getEl(this.activeIdx); } @@ -186,7 +200,7 @@ export class MenuService { cItem.parent.active = true; cItem = cItem.parent; } - this.changed.next(item); + this.changedActiveItem.next(item); } changeActive(offset = 1):boolean { diff --git a/manual-types/index.d.ts b/manual-types/index.d.ts index 8d37c8e6..1a39435e 100644 --- a/manual-types/index.d.ts +++ b/manual-types/index.d.ts @@ -6,6 +6,7 @@ declare module "scrollparent" declare module "slugify" declare module "url" declare module "json-pointer"; +declare module "mark.js"; declare module "*.css" { const content: string; diff --git a/package.json b/package.json index 96f0c3c9..4ce9ea78 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", + "mark.js": "github:julmot/mark.js", "openapi-sampler": "^0.3.3", "prismjs": "^1.5.1", "remarkable": "^1.6.2",