mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-10 19:06:34 +03:00
feat: add marker
This commit is contained in:
parent
ecf33d2dca
commit
1ff2bd84cc
|
@ -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",
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
50
src/services/MarkerService.ts
Normal file
50
src/services/MarkerService.ts
Normal 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 = '';
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue
Block a user