mirror of
https://github.com/Redocly/redoc.git
synced 2025-02-07 13:30:33 +03:00
feat: add marker
This commit is contained in:
parent
ecf33d2dca
commit
1ff2bd84cc
|
@ -80,6 +80,7 @@
|
||||||
"react-dom": "^16.0.0"
|
"react-dom": "^16.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/mark.js": "^8.11.0",
|
||||||
"@types/marked": "^0.3.0",
|
"@types/marked": "^0.3.0",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"decko": "^1.2.0",
|
"decko": "^1.2.0",
|
||||||
|
@ -88,6 +89,7 @@
|
||||||
"json-pointer": "^0.6.0",
|
"json-pointer": "^0.6.0",
|
||||||
"json-schema-ref-parser": "^4.0.4",
|
"json-schema-ref-parser": "^4.0.4",
|
||||||
"lunr": "^2.1.5",
|
"lunr": "^2.1.5",
|
||||||
|
"mark.js": "^8.11.1",
|
||||||
"marked": "^0.3.12",
|
"marked": "^0.3.12",
|
||||||
"mobx": "^3.3.0",
|
"mobx": "^3.3.0",
|
||||||
"mobx-react": "^4.3.3",
|
"mobx-react": "^4.3.3",
|
||||||
|
|
|
@ -32,7 +32,7 @@ export class Redoc extends React.Component<RedocProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { store: { spec, menu, options, search } } = this.props;
|
const { store: { spec, menu, options, search, marker } } = this.props;
|
||||||
const store = this.props.store;
|
const store = this.props.store;
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={options.theme}>
|
<ThemeProvider theme={options.theme}>
|
||||||
|
@ -42,6 +42,7 @@ export class Redoc extends React.Component<RedocProps> {
|
||||||
<ApiLogo info={spec.info} />
|
<ApiLogo info={spec.info} />
|
||||||
<SearchBox
|
<SearchBox
|
||||||
search={search}
|
search={search}
|
||||||
|
marker={marker}
|
||||||
getItemById={menu.getItemById}
|
getItemById={menu.getItemById}
|
||||||
onActivate={menu.activateAndScroll}
|
onActivate={menu.activateAndScroll}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,6 +6,8 @@ import { IMenuItem } from '../../services/MenuStore';
|
||||||
import { SearchStore } from '../../services/SearchStore';
|
import { SearchStore } from '../../services/SearchStore';
|
||||||
import { MenuItem } from '../SideMenu/MenuItem';
|
import { MenuItem } from '../SideMenu/MenuItem';
|
||||||
import { MenuItemLabel } from '../SideMenu/styled.elements';
|
import { MenuItemLabel } from '../SideMenu/styled.elements';
|
||||||
|
import { MarkerService } from '../../services/MarkerService';
|
||||||
|
import { SearchDocument } from '../../services/SearchWorker.worker';
|
||||||
|
|
||||||
const SearchInput = styled.input.attrs({
|
const SearchInput = styled.input.attrs({
|
||||||
className: 'search-input',
|
className: 'search-input',
|
||||||
|
@ -77,6 +79,7 @@ const SearchResultsBox = styled.div.attrs({
|
||||||
|
|
||||||
export interface SearchBoxProps {
|
export interface SearchBoxProps {
|
||||||
search: SearchStore;
|
search: SearchStore;
|
||||||
|
marker: MarkerService;
|
||||||
getItemById: (id: string) => IMenuItem | undefined;
|
getItemById: (id: string) => IMenuItem | undefined;
|
||||||
onActivate: (item: IMenuItem) => void;
|
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>) => {
|
search = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const q = event.target.value;
|
const q = event.target.value;
|
||||||
if (q.length < 3) {
|
if (q.length < 3) {
|
||||||
this.setState({
|
this.clearResults(q);
|
||||||
term: q,
|
|
||||||
results: [],
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
term: q,
|
term: q,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.search.search(event.target.value).then(res => {
|
this.props.search.search(event.target.value).then(res => {
|
||||||
this.setState({
|
this.setResults(res, q);
|
||||||
results: res,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
clearIfEsq = event => {
|
|
||||||
if (event && event.keyCode === 27) {
|
|
||||||
// escape
|
|
||||||
this.setState({ term: '', results: [] });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const items: IMenuItem[] = this.state.results.map(res => this.props.getItemById(res.id));
|
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));
|
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 { OpenAPISpec } from '../types';
|
||||||
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
|
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
|
||||||
import { MenuStore } from './MenuStore';
|
import { MenuStore } from './MenuStore';
|
||||||
|
@ -5,6 +7,7 @@ import { SpecStore } from './models';
|
||||||
import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOptions';
|
||||||
import { ScrollService } from './ScrollService';
|
import { ScrollService } from './ScrollService';
|
||||||
import { SearchStore } from './SearchStore';
|
import { SearchStore } from './SearchStore';
|
||||||
|
import { MarkerService } from './MarkerService';
|
||||||
|
|
||||||
interface StoreData {
|
interface StoreData {
|
||||||
menu: {
|
menu: {
|
||||||
|
@ -44,8 +47,10 @@ export class AppStore {
|
||||||
rawOptions: RedocRawOptions;
|
rawOptions: RedocRawOptions;
|
||||||
options: RedocNormalizedOptions;
|
options: RedocNormalizedOptions;
|
||||||
search: SearchStore;
|
search: SearchStore;
|
||||||
|
marker = new MarkerService();
|
||||||
|
|
||||||
private scroll: ScrollService;
|
private scroll: ScrollService;
|
||||||
|
private disposer;
|
||||||
|
|
||||||
constructor(spec: OpenAPISpec, specUrl?: string, options: RedocRawOptions = {}) {
|
constructor(spec: OpenAPISpec, specUrl?: string, options: RedocRawOptions = {}) {
|
||||||
this.rawOptions = options;
|
this.rawOptions = options;
|
||||||
|
@ -55,11 +60,35 @@ export class AppStore {
|
||||||
this.menu = new MenuStore(this.spec, this.scroll);
|
this.menu = new MenuStore(this.spec, this.scroll);
|
||||||
|
|
||||||
this.search = new SearchStore(this.spec);
|
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() {
|
dispose() {
|
||||||
this.scroll.dispose();
|
this.scroll.dispose();
|
||||||
this.menu.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
|
* 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
|
* whether sidebar with menu is opened or not
|
||||||
|
|
|
@ -7,3 +7,7 @@ export * from './SpecStore';
|
||||||
export * from './ClipboardService';
|
export * from './ClipboardService';
|
||||||
export * from './HistoryService';
|
export * from './HistoryService';
|
||||||
export * from './models';
|
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"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lunr/-/lunr-2.1.5.tgz#afb90226a6d2eb472eb1732cef7493a02b0177fd"
|
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":
|
"@types/marked@^0.3.0":
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.3.0.tgz#583c223dd33385a1dda01aaf77b0cd0411c4b524"
|
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.3.0.tgz#583c223dd33385a1dda01aaf77b0cd0411c4b524"
|
||||||
|
@ -5098,6 +5104,10 @@ map-visit@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
object-visit "^1.0.0"
|
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:
|
marked@^0.3.12:
|
||||||
version "0.3.17"
|
version "0.3.17"
|
||||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.17.tgz#607f06668b3c6b1246b28f13da76116ac1aa2d2b"
|
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.17.tgz#607f06668b3c6b1246b28f13da76116ac1aa2d2b"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user