mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-26 02:23:43 +03:00
Redoc search styling
This commit is contained in:
parent
18fe4bd748
commit
072ab15cae
|
@ -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">
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user