mirror of
				https://github.com/Redocly/redoc.git
				synced 2025-11-01 00:07:32 +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,31 +98,50 @@ 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() { | ||||
|  |  | |||
|  | @ -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