feat: add marker

This commit is contained in:
Roman Hotsiy 2018-02-22 11:26:53 +02:00
parent ecf33d2dca
commit 1ff2bd84cc
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
8 changed files with 134 additions and 16 deletions

View File

@ -80,6 +80,7 @@
"react-dom": "^16.0.0"
},
"dependencies": {
"@types/mark.js": "^8.11.0",
"@types/marked": "^0.3.0",
"classnames": "^2.2.5",
"decko": "^1.2.0",
@ -88,6 +89,7 @@
"json-pointer": "^0.6.0",
"json-schema-ref-parser": "^4.0.4",
"lunr": "^2.1.5",
"mark.js": "^8.11.1",
"marked": "^0.3.12",
"mobx": "^3.3.0",
"mobx-react": "^4.3.3",

View File

@ -32,7 +32,7 @@ export class Redoc extends React.Component<RedocProps> {
}
render() {
const { store: { spec, menu, options, search } } = this.props;
const { store: { spec, menu, options, search, marker } } = this.props;
const store = this.props.store;
return (
<ThemeProvider theme={options.theme}>
@ -42,6 +42,7 @@ export class Redoc extends React.Component<RedocProps> {
<ApiLogo info={spec.info} />
<SearchBox
search={search}
marker={marker}
getItemById={menu.getItemById}
onActivate={menu.activateAndScroll}
/>

View File

@ -6,6 +6,8 @@ import { IMenuItem } from '../../services/MenuStore';
import { SearchStore } from '../../services/SearchStore';
import { MenuItem } from '../SideMenu/MenuItem';
import { MenuItemLabel } from '../SideMenu/styled.elements';
import { MarkerService } from '../../services/MarkerService';
import { SearchDocument } from '../../services/SearchWorker.worker';
const SearchInput = styled.input.attrs({
className: 'search-input',
@ -77,6 +79,7 @@ const SearchResultsBox = styled.div.attrs({
export interface SearchBoxProps {
search: SearchStore;
marker: MarkerService;
getItemById: (id: string) => IMenuItem | undefined;
onActivate: (item: IMenuItem) => void;
}
@ -95,33 +98,52 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
};
}
clearResults(term: string) {
this.setState({
results: [],
term,
});
this.props.marker.unmark();
}
clear() {
this.setState({
results: [],
term: '',
});
this.props.marker.unmark();
}
clearIfEsq = event => {
if (event && event.keyCode === 27) {
this.clear();
}
};
setResults(results: SearchDocument[], term: string) {
this.setState({
results,
term,
});
this.props.marker.mark(term);
}
search = (event: React.ChangeEvent<HTMLInputElement>) => {
const q = event.target.value;
if (q.length < 3) {
this.setState({
term: q,
results: [],
});
this.clearResults(q);
return;
}
this.setState({
term: q,
});
this.props.search.search(event.target.value).then(res => {
this.setState({
results: res,
});
this.setResults(res, q);
});
};
clearIfEsq = event => {
if (event && event.keyCode === 27) {
// escape
this.setState({ term: '', results: [] });
}
};
render() {
const items: IMenuItem[] = this.state.results.map(res => this.props.getItemById(res.id));
items.sort((a, b) => (a.depth > b.depth ? 1 : a.depth < b.depth ? -1 : 0));

View File

@ -1,3 +1,5 @@
import { observe } from 'mobx';
import { OpenAPISpec } from '../types';
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
import { MenuStore } from './MenuStore';
@ -5,6 +7,7 @@ import { SpecStore } from './models';
import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOptions';
import { ScrollService } from './ScrollService';
import { SearchStore } from './SearchStore';
import { MarkerService } from './MarkerService';
interface StoreData {
menu: {
@ -44,8 +47,10 @@ export class AppStore {
rawOptions: RedocRawOptions;
options: RedocNormalizedOptions;
search: SearchStore;
marker = new MarkerService();
private scroll: ScrollService;
private disposer;
constructor(spec: OpenAPISpec, specUrl?: string, options: RedocRawOptions = {}) {
this.rawOptions = options;
@ -55,11 +60,35 @@ export class AppStore {
this.menu = new MenuStore(this.spec, this.scroll);
this.search = new SearchStore(this.spec);
this.disposer = observe(this.menu, 'activeItemIdx', change => {
this.updateMarkOnMenu(change.newValue as number);
});
}
updateMarkOnMenu(idx: number) {
console.log('update marker');
const start = Math.max(0, idx);
const end = Math.min(this.menu.flatItems.length, start + 5);
const elements: Element[] = [];
for (let i = start; i < end; i++) {
let elem = this.menu.getElementAt(i);
if (!elem) continue;
if (this.menu.flatItems[i].type === 'section') {
elem = elem.parentElement!.parentElement;
}
if (elem) elements.push(elem);
}
this.marker.addOnly(elements);
this.marker.mark();
}
dispose() {
this.scroll.dispose();
this.menu.dispose();
this.disposer();
}
/**

View File

@ -0,0 +1,50 @@
import * as Mark from 'mark.js';
export class MarkerService {
map: Map<Element, Mark> = new Map();
private prevTerm: string = '';
add(el: HTMLElement) {
this.map.set(el, new Mark(el));
}
delete(el: Element) {
this.map.delete(el);
}
addOnly(elements: Element[]) {
this.map.forEach((inst, elem) => {
if (elements.indexOf(elem) === -1) {
inst.unmark();
this.map.delete(elem);
}
});
for (let el of elements) {
if (!this.map.has(el)) {
this.map.set(el, new Mark(el as HTMLElement));
}
}
}
clearAll() {
this.unmark();
this.map.clear();
}
mark(term?: string) {
console.log('mark', term);
if (!term && !this.prevTerm) return;
this.map.forEach(val => {
val.unmark();
val.mark(term || this.prevTerm);
});
this.prevTerm = term || this.prevTerm || '';
}
unmark() {
this.map.forEach(val => val.unmark());
this.prevTerm = '';
}
}

View File

@ -37,7 +37,7 @@ export class MenuStore {
/**
* active item absolute index (when flattened). -1 means nothing is selected
*/
activeItemIdx: number = -1;
@observable activeItemIdx: number = -1;
/**
* whether sidebar with menu is opened or not

View File

@ -7,3 +7,7 @@ export * from './SpecStore';
export * from './ClipboardService';
export * from './HistoryService';
export * from './models';
export * from './RedocNormalizedOptions';
export * from './MenuBuilder';
export * from './SearchStore';
export * from './MarkerService';

View File

@ -112,6 +112,12 @@
version "2.1.5"
resolved "https://registry.yarnpkg.com/@types/lunr/-/lunr-2.1.5.tgz#afb90226a6d2eb472eb1732cef7493a02b0177fd"
"@types/mark.js@^8.11.0":
version "8.11.0"
resolved "https://registry.yarnpkg.com/@types/mark.js/-/mark.js-8.11.0.tgz#1d507352c30f020a35213f80b5131d8ffba194d7"
dependencies:
"@types/jquery" "*"
"@types/marked@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.3.0.tgz#583c223dd33385a1dda01aaf77b0cd0411c4b524"
@ -5098,6 +5104,10 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
mark.js@^8.11.1:
version "8.11.1"
resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5"
marked@^0.3.12:
version "0.3.17"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.17.tgz#607f06668b3c6b1246b28f13da76116ac1aa2d2b"