Redoc search styling

This commit is contained in:
Roman Hotsiy 2017-01-28 18:47:12 +02:00
parent 18fe4bd748
commit 072ab15cae
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
11 changed files with 119 additions and 46 deletions

View File

@ -8,8 +8,10 @@
<div class="background-actual"> </div> <div class="background-actual"> </div>
</div> </div>
<div class="menu-content" sticky-sidebar [scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset"> <div class="menu-content" sticky-sidebar [scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset">
<api-logo> </api-logo> <div class="menu-header">
<redoc-search> </redoc-search> <api-logo> </api-logo>
<redoc-search> </redoc-search>
</div>
<side-menu> </side-menu> <side-menu> </side-menu>
</div> </div>
<div class="api-content"> <div class="api-content">

View File

@ -40,12 +40,17 @@
.menu-content { .menu-content {
overflow: hidden; overflow: hidden;
display: flex;
flex-direction: column;
}
side-menu {
overflow-y: auto;
} }
[sticky-sidebar] { [sticky-sidebar] {
width: $side-bar-width; width: $side-bar-width;
background-color: $side-bar-bg-color; background-color: $side-bar-bg-color;
overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
transform: translateZ(0); transform: translateZ(0);
z-index: 75; z-index: 75;

View File

@ -1,6 +1,10 @@
<input #search (keyup)="update(search.value)" placeholder="Search"> <div class="search-input-wrap">
<ul class="search-results"> <input #search (keyup)="update(search.value)" placeholder="Search">
<li class="result" *ngFor="let item of items" (click)="clickSearch(item)"> </div>
{{item?.menuItem?.name}} <ul class="search-results" [hidden]="!items.length">
<li class="result" *ngFor="let item of items"
ngClass="menu-item-depth-{{item.menuItem.depth}} {{item.menuItem.ready ? '' : 'disabled'}}"
(click)="clickSearch(item)">
{{item.menuItem.name}}
<li> <li>
</ul> </ul>

View File

@ -1,22 +1,61 @@
@import '../../shared/styles/variables';
:host { :host {
display: block; display: block;
padding: 20px; margin: 10px 0;
background: silver; }
.search-input-wrap {
padding: 0 20px;
} }
input { input {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 5px; padding: 5px;
border: 0;
border-bottom: 1px solid darken($side-bar-bg-color, 10%);
font-weight: bold;
font-size: 13px;
color: $text-color;
background-color: transparent;
outline: none;
} }
.search-results { .search-results {
margin: 10px 0 0; margin: 10px 0 0;
padding: 0;
list-style: none; list-style: none;
padding: 10px 0;
background-color: darken($side-bar-bg-color, 5%);
max-height: 100px;
overflow-y: auto;
border-bottom: 1px solid darken($side-bar-bg-color, 10%);
border-top: 1px solid darken($side-bar-bg-color, 10%);
min-height: 150px;
max-height: 250px;
> li { > li {
display: block; display: block;
cursor: pointer; cursor: pointer;
font-family: Montserrat, sans-serif;
font-size: 13px;
padding: 5px 20px;
&:hover {
background-color: darken($side-bar-bg-color, 10%);
}
}
li.menu-item-depth-1 {
color: #0033a0;
text-transform: uppercase;
}
> li.disabled {
cursor: default;
color: lighten($text-color, 60%);
} }
} }

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
import { Component, ChangeDetectionStrategy, OnInit, HostBinding } from '@angular/core'; import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, HostBinding } from '@angular/core';
import { Marker, SearchService, MenuService } from '../../services/'; import { Marker, SearchService, MenuService, MenuItem } from '../../services/';
@Component({ @Component({
selector: 'redoc-search', selector: 'redoc-search',
@ -10,9 +10,16 @@ import { Marker, SearchService, MenuService } from '../../services/';
}) })
export class RedocSearch implements OnInit { export class RedocSearch implements OnInit {
logo:any = {}; logo:any = {};
items: any[] = []; items: { menuItem: MenuItem, pointers: string[] }[] = [];
constructor(private marker: Marker, public search: SearchService, public menu: MenuService) { _subscription;
constructor(
cdr: ChangeDetectorRef,
private marker: Marker,
public search: SearchService,
public menu: MenuService) {
this._subscription = menu.changed.subscribe(() => cdr.detectChanges());
} }
init() { init() {
@ -25,6 +32,11 @@ export class RedocSearch implements OnInit {
menuItem: this.menu.getItemById(id), menuItem: this.menu.getItemById(id),
pointers: searchRes[id].map(el => el.pointer) pointers: searchRes[id].map(el => el.pointer)
})); }));
this.items.sort((a, b) => {
if (a.menuItem.depth > b.menuItem.depth) return 1;
else if (a.menuItem.depth < b.menuItem.depth) return -1;
else return 0;
});
this.marker.mark(val); this.marker.mark(val);
} }
@ -40,4 +52,8 @@ export class RedocSearch implements OnInit {
ngOnInit() { ngOnInit() {
this.init(); this.init();
} }
destroy() {
this._subscription.unsubscribe();
}
} }

View File

@ -1,12 +1,10 @@
<div #mobile class="mobile-nav" (click)="toggleMobileNav()"> <div #mobile class="mobile-nav" (click)="toggleMobileNav()">
<span class="menu-header"> API Reference: </span>
<span class="selected-item-info"> <span class="selected-item-info">
<span class="selected-tag"> {{activeCatCaption}} </span> <span class="selected-tag"> {{activeCatCaption}} </span>
<span class="selected-endpoint">{{activeItemCaption}}</span> <span class="selected-endpoint">{{activeItemCaption}}</span>
</span> </span>
</div> </div>
<div #desktop id="resources-nav"> <div #desktop id="resources-nav">
<h5 class="menu-header"> API reference </h5>
<ul class="menu-root"> <ul class="menu-root">
<side-menu-items [items]="menuItems" (activate)="activateAndScroll($event)"></side-menu-items> <side-menu-items [items]="menuItems" (activate)="activateAndScroll($event)"></side-menu-items>
</ul> </ul>

View File

@ -11,13 +11,6 @@ ul.menu-root {
padding: 0; padding: 0;
} }
.menu-header {
text-transform: uppercase;
color: $headers-color;
padding: 0 $side-menu-item-hpadding;
margin: 10px 0;
}
.mobile-nav { .mobile-nav {
display: none; display: none;
height: 3em; height: 3em;
@ -39,15 +32,6 @@ ul.menu-root {
float: right; float: right;
vertical-align: middle; vertical-align: middle;
} }
.menu-header {
padding: 0 10px 0 20px;
font-size: 0.95em;
@media (max-width: $mobile-menu-compact-breakpoint) {
display: none;
}
}
} }
@media (max-width: $side-menu-mobile-breakpoint) { @media (max-width: $side-menu-mobile-breakpoint) {
@ -61,10 +45,6 @@ ul.menu-root {
transition: all 0.3s ease; transition: all 0.3s ease;
} }
#resources-nav .menu-header {
display: none;
}
.menu-subitems { .menu-subitems {
height: auto; height: auto;
} }

View File

@ -60,7 +60,7 @@ export abstract class BaseSearchableComponent extends BaseComponent implements O
subscribeForSearch() { subscribeForSearch() {
this.searchSubscription = this.app.searchContainingPointers.subscribe(ptrs => { this.searchSubscription = this.app.searchContainingPointers.subscribe(ptrs => {
for (let i = 0; i < ptrs.length; ++i) { for (let i = 0; i < ptrs.length; ++i) {
this.ensureSearchIsShown(ptrs[i]); if (ptrs[i]) this.ensureSearchIsShown(ptrs[i]);
} }
}); });
} }

View File

@ -11,7 +11,7 @@ export class AppStateService {
loading = new Subject<boolean>(); loading = new Subject<boolean>();
initialized = new BehaviorSubject<any>(false); initialized = new BehaviorSubject<any>(false);
searchContainingPointers = new BehaviorSubject<string[]>([]); searchContainingPointers = new BehaviorSubject<string|null[]>([]);
startLoading() { startLoading() {
this.loading.next(true); this.loading.next(true);

View File

@ -34,7 +34,7 @@ export interface MenuItem {
active?: boolean; active?: boolean;
ready?: boolean; ready?: boolean;
level?: number; depth?: number;
flatIdx?: number; flatIdx?: number;
metadata?: any; metadata?: any;

View File

@ -2,6 +2,14 @@ import { Injectable } from '@angular/core';
import { AppStateService } from './app-state.service'; import { AppStateService } from './app-state.service';
import { SchemaNormalizer } from './schema-normalizer.service'; import { SchemaNormalizer } from './schema-normalizer.service';
import { JsonPointer, groupBy, SpecManager, StringMap, snapshot } from '../utils/'; import { JsonPointer, groupBy, SpecManager, StringMap, snapshot } from '../utils/';
import * as slugify from 'slugify';
import {
Spec as SwaggerSpec,
Operation as SwaggerOperation,
Schema as SwaggerSchema,
BodyParameter
} from '@types/swagger-schema-official';
import * as lunr from 'lunr'; import * as lunr from 'lunr';
@ -12,6 +20,10 @@ interface IndexElement {
pointer: string; pointer: string;
} }
interface SwaggerSchemaExt extends SwaggerSchema {
_pointer?: string;
}
const index = lunr(function () { const index = lunr(function () {
//this.field('menuId', {boost: 0}); //this.field('menuId', {boost: 0});
this.field('title', {boost: 1.5}); this.field('title', {boost: 1.5});
@ -28,12 +40,13 @@ export class SearchService {
this.normalizer = new SchemaNormalizer(spec); this.normalizer = new SchemaNormalizer(spec);
} }
ensureSearchVisible(containingPointers: string[]) { ensureSearchVisible(containingPointers: string|null[]) {
this.app.searchContainingPointers.next(containingPointers); this.app.searchContainingPointers.next(containingPointers);
} }
indexAll() { indexAll() {
this.indexPaths(this.spec.schema); this.indexPaths(this.spec.schema);
this.indexTags(this.spec.schema);
} }
search(q):StringMap<IndexElement[]> { search(q):StringMap<IndexElement[]> {
@ -53,7 +66,21 @@ export class SearchService {
store[element.pointer] = element; store[element.pointer] = element;
} }
indexPaths(swagger:any) { indexTags(swagger:SwaggerSpec) {
let tags = swagger.tags;
for (let tag of tags) {
if (tag['x-traitTag']) continue;
let id = `tag/${slugify(tag.name)}`;
this.index({
menuId: id,
title: tag.name,
body: tag.description,
pointer: id
});
}
}
indexPaths(swagger:SwaggerSpec) {
const paths = swagger.paths; const paths = swagger.paths;
const basePtr = '#/paths'; const basePtr = '#/paths';
Object.keys(paths).forEach(path => { Object.keys(paths).forEach(path => {
@ -66,7 +93,7 @@ export class SearchService {
}); });
} }
indexOperation(operation:any, operationPointer:string) { indexOperation(operation:SwaggerOperation, operationPointer:string) {
this.index({ this.index({
pointer: operationPointer, pointer: operationPointer,
menuId: operationPointer, menuId: operationPointer,
@ -77,7 +104,7 @@ export class SearchService {
this.indexOperationParameters(operation, operationPointer); this.indexOperationParameters(operation, operationPointer);
} }
indexOperationParameters(operation: any, operationPointer: string) { indexOperationParameters(operation: SwaggerOperation, operationPointer: string) {
const parameters = operation.parameters; const parameters = operation.parameters;
if (!parameters) return; if (!parameters) return;
for (let i=0; i<parameters.length; ++i) { for (let i=0; i<parameters.length; ++i) {
@ -92,12 +119,13 @@ export class SearchService {
if (param.in === 'body') { if (param.in === 'body') {
this.normalizer.reset(); this.normalizer.reset();
this.indexSchema(param.schema, '', JsonPointer.join(paramPointer, ['schema']), operationPointer); this.indexSchema((<BodyParameter>param).schema,
'', JsonPointer.join(paramPointer, ['schema']), operationPointer);
} }
} }
} }
indexOperationResponses(operation:any, operationPtr:string) { indexOperationResponses(operation:SwaggerOperation, operationPtr:string) {
const responses = operation.responses; const responses = operation.responses;
if (!responses) return; if (!responses) return;
Object.keys(responses).forEach(code => { Object.keys(responses).forEach(code => {
@ -117,7 +145,8 @@ export class SearchService {
}); });
} }
indexSchema(_schema:any, name: string, absolutePointer: string, menuPointer: string, parent?: string) { indexSchema(_schema:SwaggerSchemaExt, name: string, absolutePointer: string,
menuPointer: string, parent?: string) {
if (!_schema) return; if (!_schema) return;
let schema = _schema; let schema = _schema;
let title = name; let title = name;