mirror of
				https://github.com/Redocly/redoc.git
				synced 2025-10-31 07:47:29 +03:00 
			
		
		
		
	chore: sections/markdown refactor
This commit is contained in:
		
							parent
							
								
									e0d82f4aa8
								
							
						
					
					
						commit
						eae11682b8
					
				|  | @ -1,19 +1,44 @@ | |||
| import styled, { media } from '../styled-components'; | ||||
| import { SECTION_ATTR } from '../services/MenuStore'; | ||||
| import styled, { media, withProps } from '../styled-components'; | ||||
| 
 | ||||
| export const MiddlePanel = styled.div` | ||||
|   width: calc(100% - ${props => props.theme.rightPanel.width}); | ||||
|   padding: ${props => props.theme.spacing.unit * 8}px; | ||||
|   padding: 0 ${props => props.theme.spacing.unit * 8}px; | ||||
| 
 | ||||
|   ${media.lessThan('medium')` | ||||
|     width: 100%; | ||||
|   `};
 | ||||
| `;
 | ||||
| 
 | ||||
| export const Section = withProps<{ underlined?: boolean }>( | ||||
|   styled.div.attrs({ | ||||
|     [SECTION_ATTR]: props => props.id, | ||||
|   } as any), | ||||
| )` | ||||
|   padding: ${props => props.theme.spacing.unit * 8}px 0; | ||||
| 
 | ||||
|   ${props => | ||||
|     (props.underlined && | ||||
|       ` | ||||
|     position: relative; | ||||
| 
 | ||||
|     &:not(:last-of-type):after { | ||||
|       position: absolute; | ||||
|       bottom: 0; | ||||
|       width: 100%; | ||||
|       display: block; | ||||
|       content: ''; | ||||
|       border-bottom: 1px solid rgba(0, 0, 0, 0.2); | ||||
|     } | ||||
|   `) ||
 | ||||
|     ''} | ||||
| `;
 | ||||
| 
 | ||||
| export const RightPanel = styled.div` | ||||
|   width: ${props => props.theme.rightPanel.width}; | ||||
|   color: #fafbfc; | ||||
|   background-color: ${props => props.theme.rightPanel.backgroundColor}; | ||||
|   padding: ${props => props.theme.spacing.unit * 8}px; | ||||
|   padding: 0 ${props => props.theme.spacing.unit * 8}px; | ||||
| 
 | ||||
|   ${media.lessThan('medium')` | ||||
|     width: 100%; | ||||
|  | @ -27,6 +52,7 @@ export const DarkRightPanel = RightPanel.extend` | |||
| export const Row = styled.div` | ||||
|   display: flex; | ||||
|   width: 100%; | ||||
|   padding: 0; | ||||
| 
 | ||||
|   ${media.lessThan('medium')` | ||||
|     flex-direction: column; | ||||
|  |  | |||
|  | @ -2,6 +2,8 @@ import * as React from 'react'; | |||
| 
 | ||||
| import PerfectScrollbarType, * as PerfectScrollbarNamespace from 'perfect-scrollbar'; | ||||
| import psStyles from 'perfect-scrollbar/css/perfect-scrollbar.css'; | ||||
| 
 | ||||
| import { OptionsContext } from '../components/OptionsProvider'; | ||||
| import styled, { injectGlobal } from '../styled-components'; | ||||
| 
 | ||||
| /* | ||||
|  | @ -18,11 +20,13 @@ const StyledScrollWrapper = styled.div` | |||
|   position: relative; | ||||
| `;
 | ||||
| 
 | ||||
| export class PerfectScrollbar extends React.Component<{ | ||||
| export interface PerfectScrollbarProps { | ||||
|   options?: PerfectScrollbarType.Options; | ||||
|   className?: string; | ||||
|   updateFn: (fn) => void; | ||||
| }> { | ||||
|   updateFn?: (fn) => void; | ||||
| } | ||||
| 
 | ||||
| export class PerfectScrollbar extends React.Component<PerfectScrollbarProps> { | ||||
|   private _container: HTMLElement; | ||||
|   private inst: PerfectScrollbarType; | ||||
| 
 | ||||
|  | @ -49,7 +53,9 @@ export class PerfectScrollbar extends React.Component<{ | |||
|   render() { | ||||
|     const { children, className, updateFn } = this.props; | ||||
| 
 | ||||
|     if (updateFn) { | ||||
|       updateFn(this.componentDidUpdate.bind(this)); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <StyledScrollWrapper className={`scrollbar-container ${className}`} innerRef={this.handleRef}> | ||||
|  | @ -58,3 +64,26 @@ export class PerfectScrollbar extends React.Component<{ | |||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function PerfectScrollbarWrap( | ||||
|   props: PerfectScrollbarProps & { children: JSX.Element[] | JSX.Element }, | ||||
| ) { | ||||
|   return ( | ||||
|     <OptionsContext.Consumer> | ||||
|       {options => | ||||
|         !options.nativeScrollbars ? ( | ||||
|           <PerfectScrollbar {...props}>{props.children}</PerfectScrollbar> | ||||
|         ) : ( | ||||
|           <div | ||||
|             style={{ | ||||
|               overflow: 'auto', | ||||
|               msOverflowStyle: '-ms-autohiding-scrollbar', | ||||
|             }} | ||||
|           > | ||||
|             {props.children} | ||||
|           </div> | ||||
|         ) | ||||
|       } | ||||
|     </OptionsContext.Consumer> | ||||
|   ); | ||||
| } | ||||
|  |  | |||
|  | @ -1,22 +0,0 @@ | |||
| import * as React from 'react'; | ||||
| 
 | ||||
| import { MiddlePanel, Row } from '../../common-elements/'; | ||||
| 
 | ||||
| import { Markdown } from '../Markdown/Markdown'; | ||||
| 
 | ||||
| export interface ApiDescriptionProps { | ||||
|   description: string; | ||||
| } | ||||
| 
 | ||||
| export class ApiDescription extends React.PureComponent<ApiDescriptionProps> { | ||||
|   render() { | ||||
|     const { description } = this.props; | ||||
|     return ( | ||||
|       <Row> | ||||
|         <MiddlePanel> | ||||
|           <Markdown source={description} /> | ||||
|         </MiddlePanel> | ||||
|       </Row> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | @ -3,8 +3,8 @@ import * as React from 'react'; | |||
| 
 | ||||
| import { AppStore } from '../../services/AppStore'; | ||||
| 
 | ||||
| import { MiddlePanel, Row } from '../../common-elements/'; | ||||
| 
 | ||||
| import { MiddlePanel, Row, Section } from '../../common-elements/'; | ||||
| import { Markdown } from '../Markdown/Markdown'; | ||||
| import { StyledMarkdownBlock } from '../Markdown/styled.elements'; | ||||
| import { | ||||
|   ApiHeader, | ||||
|  | @ -70,6 +70,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> { | |||
|       null; | ||||
| 
 | ||||
|     return ( | ||||
|       <Section> | ||||
|         <Row> | ||||
|           <MiddlePanel className="api-info"> | ||||
|             <ApiHeader> | ||||
|  | @ -105,8 +106,10 @@ export class ApiInfo extends React.Component<ApiInfoProps> { | |||
|               )) || | ||||
|                 null} | ||||
|             </StyledMarkdownBlock> | ||||
|             <Markdown source={store.spec.info.description} /> | ||||
|           </MiddlePanel> | ||||
|         </Row> | ||||
|       </Section> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,2 +1 @@ | |||
| export { ApiDescription } from './ApiDescription'; | ||||
| export { ApiInfo } from './ApiInfo'; | ||||
|  |  | |||
|  | @ -1,10 +1,9 @@ | |||
| import { observer } from 'mobx-react'; | ||||
| import * as React from 'react'; | ||||
| 
 | ||||
| import { SECTION_ATTR } from '../../services/MenuStore'; | ||||
| import { Markdown } from '../Markdown/Markdown'; | ||||
| import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown'; | ||||
| 
 | ||||
| import { H1, H2, MiddlePanel, Row, ShareLink } from '../../common-elements'; | ||||
| import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements'; | ||||
| import { MDXComponentMeta } from '../../services/MarkdownRenderer'; | ||||
| import { ContentItemModel } from '../../services/MenuBuilder'; | ||||
| import { GroupModel, OperationModel } from '../../services/models'; | ||||
|  | @ -12,20 +11,22 @@ import { Operation } from '../Operation/Operation'; | |||
| import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes'; | ||||
| import { StoreConsumer } from '../StoreBuilder'; | ||||
| 
 | ||||
| @observer | ||||
| export class ContentItems extends React.Component<{ | ||||
|   items: ContentItemModel[]; | ||||
|   allowedMdComponents?: Dict<MDXComponentMeta>; | ||||
| }> { | ||||
|   static defaultProps = { | ||||
|     allowedMdComponents: { | ||||
| const DEFAULT_ALLOWED_COMPONENTS = { | ||||
|   'security-definitions': { | ||||
|     component: SecurityDefs, | ||||
|     propsSelector: _store => ({ | ||||
|       securitySchemes: _store!.spec.securitySchemes, | ||||
|     }), | ||||
|   }, | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| @observer | ||||
| export class ContentItems extends React.Component<{ | ||||
|   items: ContentItemModel[]; | ||||
|   allowedMdComponents?: Dict<MDXComponentMeta>; | ||||
| }> { | ||||
|   static defaultProps = { | ||||
|     allowedMdComponents: DEFAULT_ALLOWED_COMPONENTS, | ||||
|   }; | ||||
| 
 | ||||
|   render() { | ||||
|  | @ -34,14 +35,18 @@ export class ContentItems extends React.Component<{ | |||
|       return null; | ||||
|     } | ||||
|     return items.map(item => ( | ||||
|       <ContentItem item={item} key={item.id} allowedMdComponents={this.props.allowedMdComponents} /> | ||||
|       <ContentItem | ||||
|         item={item} | ||||
|         key={item.id} | ||||
|         allowedMdComponents={this.props.allowedMdComponents!} | ||||
|       /> | ||||
|     )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export interface ContentItemProps { | ||||
|   item: ContentItemModel; | ||||
|   allowedMdComponents?: Dict<MDXComponentMeta>; | ||||
|   allowedMdComponents: Dict<MDXComponentMeta>; | ||||
| } | ||||
| 
 | ||||
| @observer | ||||
|  | @ -67,15 +72,21 @@ export class ContentItem extends React.Component<ContentItemProps> { | |||
|         throw new Error('Unknown item type'); | ||||
|     } | ||||
| 
 | ||||
|     return [ | ||||
|       <div key="section" {...{ [SECTION_ATTR]: item.id }}> | ||||
|     return ( | ||||
|       <> | ||||
|         <Section id={item.id} underlined={item.type === 'section'}> | ||||
|           {content} | ||||
|       </div>, | ||||
|       (item as any).items && <ContentItems key="content" items={(item as any).items} />, | ||||
|     ]; | ||||
|         </Section> | ||||
|         {item.items && ( | ||||
|           <ContentItems items={item.items} allowedMdComponents={this.props.allowedMdComponents} /> | ||||
|         )} | ||||
|       </> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const middlePanelWrap = component => <MiddlePanel>{component}</MiddlePanel>; | ||||
| 
 | ||||
| @observer | ||||
| export class SectionItem extends React.Component<ContentItemProps> { | ||||
|   render() { | ||||
|  | @ -83,23 +94,26 @@ export class SectionItem extends React.Component<ContentItemProps> { | |||
|     const components = this.props.allowedMdComponents; | ||||
|     const Header = level === 2 ? H2 : H1; | ||||
|     return ( | ||||
|       <> | ||||
|         <Row> | ||||
|           <MiddlePanel> | ||||
|             <Header> | ||||
|               <ShareLink href={'#' + this.props.item.id} /> | ||||
|               {name} | ||||
|             </Header> | ||||
|           {components ? ( | ||||
|             <StoreConsumer> | ||||
|               {store => ( | ||||
|                 <Markdown source={description || ''} allowedComponents={components} store={store} /> | ||||
|               )} | ||||
|             </StoreConsumer> | ||||
|           ) : ( | ||||
|             <Markdown source={description || ''} /> | ||||
|           )} | ||||
|           </MiddlePanel> | ||||
|         </Row> | ||||
|         <StoreConsumer> | ||||
|           {store => ( | ||||
|             <AdvancedMarkdown | ||||
|               source={description || ''} | ||||
|               allowedComponents={components} | ||||
|               store={store} | ||||
|               htmlWrap={middlePanelWrap} | ||||
|             /> | ||||
|           )} | ||||
|         </StoreConsumer> | ||||
|       </> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										38
									
								
								src/components/Markdown/AdvancedMarkdown.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/components/Markdown/AdvancedMarkdown.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| import * as React from 'react'; | ||||
| 
 | ||||
| import { AppStore, MarkdownRenderer, MDXComponentMeta } from '../../services'; | ||||
| import { BaseMarkdownProps } from './Markdown'; | ||||
| import { SanitizedMarkdownHTML } from './SanitizedMdBlock'; | ||||
| 
 | ||||
| export interface AdvancedMarkdownProps extends BaseMarkdownProps { | ||||
|   store?: AppStore; | ||||
|   allowedComponents: Dict<MDXComponentMeta>; | ||||
|   htmlWrap?: (part: JSX.Element) => JSX.Element; | ||||
| } | ||||
| 
 | ||||
| export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> { | ||||
|   render() { | ||||
|     const { store, source, allowedComponents, htmlWrap = i => i } = this.props; | ||||
| 
 | ||||
|     if (!store) { | ||||
|       throw new Error('When using componentes in markdown, store prop must be provided'); | ||||
|     } | ||||
| 
 | ||||
|     const renderer = new MarkdownRenderer(); | ||||
|     const parts = renderer.renderMdWithComponents(source, allowedComponents); | ||||
| 
 | ||||
|     if (!parts.length) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return parts.map((part, idx) => { | ||||
|       if (typeof part === 'string') { | ||||
|         return React.cloneElement( | ||||
|           htmlWrap(<SanitizedMarkdownHTML html={part} inline={false} dense={false} />), | ||||
|           { key: idx }, | ||||
|         ); | ||||
|       } | ||||
|       return <part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} />; | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | @ -1,103 +1,35 @@ | |||
| import * as React from 'react'; | ||||
| 
 | ||||
| import * as DOMPurify from 'dompurify'; | ||||
| import { AppStore, MarkdownRenderer, MDXComponentMeta } from '../../services'; | ||||
| import { OptionsContext } from '../OptionsProvider'; | ||||
| 
 | ||||
| import { StyledMarkdownBlock } from './styled.elements'; | ||||
| 
 | ||||
| const StyledMarkdownSpan = StyledMarkdownBlock.withComponent('span'); | ||||
| import { MarkdownRenderer } from '../../services'; | ||||
| import { SanitizedMarkdownHTML } from './SanitizedMdBlock'; | ||||
| 
 | ||||
| export interface StylingMarkdownProps { | ||||
|   dense?: boolean; | ||||
|   inline?: boolean; | ||||
| } | ||||
| 
 | ||||
| export interface BaseMarkdownProps extends StylingMarkdownProps { | ||||
| export interface BaseMarkdownProps { | ||||
|   sanitize?: boolean; | ||||
|   store?: AppStore; | ||||
| } | ||||
| 
 | ||||
| const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html); | ||||
| 
 | ||||
| function SanitizedMarkdownHTML(props: StylingMarkdownProps & { html: string }) { | ||||
|   const Wrap = props.inline ? StyledMarkdownSpan : StyledMarkdownBlock; | ||||
| 
 | ||||
|   return ( | ||||
|     <OptionsContext.Consumer> | ||||
|       {options => ( | ||||
|         <Wrap | ||||
|           className={'redoc-markdown'} | ||||
|           dangerouslySetInnerHTML={{ | ||||
|             __html: sanitize(options.untrustedSpec, props.html), | ||||
|           }} | ||||
|           {...props} | ||||
|         /> | ||||
|       )} | ||||
|     </OptionsContext.Consumer> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export interface MarkdownProps extends BaseMarkdownProps { | ||||
|   allowedComponents?: Dict<MDXComponentMeta>; | ||||
|   source: string; | ||||
| } | ||||
| 
 | ||||
| export type MarkdownProps = BaseMarkdownProps & | ||||
|   StylingMarkdownProps & { | ||||
|     source: string; | ||||
|     className?: string; | ||||
|   }; | ||||
| 
 | ||||
| export class Markdown extends React.Component<MarkdownProps> { | ||||
|   constructor(props: MarkdownProps) { | ||||
|     super(props); | ||||
| 
 | ||||
|     if (props.allowedComponents && props.inline) { | ||||
|       throw new Error('Markdown Component: "inline" mode doesn\'t support "components"'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   render() { | ||||
|     const { source, allowedComponents, store, inline, dense } = this.props; | ||||
| 
 | ||||
|     if (allowedComponents && !store) { | ||||
|       throw new Error('When using componentes in markdown, store prop must be provided'); | ||||
|     } | ||||
| 
 | ||||
|     const { source, inline, dense, className } = this.props; | ||||
|     const renderer = new MarkdownRenderer(); | ||||
|     if (allowedComponents) { | ||||
|     return ( | ||||
|         <AdvancedMarkdown | ||||
|           parts={renderer.renderMdWithComponents(source, allowedComponents)} | ||||
|           {...this.props} | ||||
|       <SanitizedMarkdownHTML | ||||
|         html={renderer.renderMd(source)} | ||||
|         inline={inline} | ||||
|         dense={dense} | ||||
|         className={className} | ||||
|       /> | ||||
|     ); | ||||
|     } else { | ||||
|       return ( | ||||
|         <SanitizedMarkdownHTML html={renderer.renderMd(source)} inline={inline} dense={dense} /> | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export interface AdvancedMarkdownProps extends BaseMarkdownProps { | ||||
|   parts: Array<string | MDXComponentMeta>; | ||||
| } | ||||
| 
 | ||||
| export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> { | ||||
|   render() { | ||||
|     const { inline, dense, store, parts } = this.props; | ||||
| 
 | ||||
|     if (!parts.length) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <> | ||||
|         {parts.map( | ||||
|           (part, idx) => | ||||
|             typeof part === 'string' ? ( | ||||
|               <SanitizedMarkdownHTML html={part} inline={inline} dense={dense} key={idx} /> | ||||
|             ) : ( | ||||
|               <part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} /> | ||||
|             ), | ||||
|         )} | ||||
|       </> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										30
									
								
								src/components/Markdown/SanitizedMdBlock.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/components/Markdown/SanitizedMdBlock.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| import * as DOMPurify from 'dompurify'; | ||||
| import * as React from 'react'; | ||||
| 
 | ||||
| import { OptionsContext } from '../OptionsProvider'; | ||||
| import { StylingMarkdownProps } from './Markdown'; | ||||
| import { StyledMarkdownBlock } from './styled.elements'; | ||||
| 
 | ||||
| const StyledMarkdownSpan = StyledMarkdownBlock.withComponent('span'); | ||||
| 
 | ||||
| const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html); | ||||
| 
 | ||||
| export function SanitizedMarkdownHTML( | ||||
|   props: StylingMarkdownProps & { html: string; className?: string }, | ||||
| ) { | ||||
|   const Wrap = props.inline ? StyledMarkdownSpan : StyledMarkdownBlock; | ||||
| 
 | ||||
|   return ( | ||||
|     <OptionsContext.Consumer> | ||||
|       {options => ( | ||||
|         <Wrap | ||||
|           className={'redoc-markdown ' + (props.className || '')} | ||||
|           dangerouslySetInnerHTML={{ | ||||
|             __html: sanitize(options.untrustedSpec, props.html), | ||||
|           }} | ||||
|           {...props} | ||||
|         /> | ||||
|       )} | ||||
|     </OptionsContext.Consumer> | ||||
|   ); | ||||
| } | ||||
|  | @ -23,20 +23,10 @@ const OperationRow = Row.extend` | |||
|   contain: content; | ||||
| 
 | ||||
|   overflow: hidden; | ||||
|   position: relative; | ||||
| 
 | ||||
|   &:after { | ||||
|     position: absolute; | ||||
|     bottom: 0; | ||||
|     width: 100%; | ||||
|     display: block; | ||||
|     content: ''; | ||||
|     border-bottom: 1px solid rgba(0, 0, 0, 0.2); | ||||
|   } | ||||
| `;
 | ||||
| 
 | ||||
| const Description = styled(Markdown)` | ||||
|   margin-bottom: ${({ theme }) => theme.spacing.unit * 8}; | ||||
|   margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px; | ||||
| `;
 | ||||
| 
 | ||||
| export interface OperationProps { | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import * as React from 'react'; | |||
| import { ThemeProvider } from '../../styled-components'; | ||||
| 
 | ||||
| import { AppStore } from '../../services'; | ||||
| import { ApiDescription, ApiInfo } from '../ApiInfo/'; | ||||
| import { ApiInfo } from '../ApiInfo/'; | ||||
| import { ApiLogo } from '../ApiLogo/ApiLogo'; | ||||
| import { ContentItems } from '../ContentItems/ContentItems'; | ||||
| import { OptionsProvider } from '../OptionsProvider'; | ||||
|  | @ -57,7 +57,6 @@ export class Redoc extends React.Component<RedocProps> { | |||
|               </StickyResponsiveSidebar> | ||||
|               <ApiContentWrap className="api-content"> | ||||
|                 <ApiInfo store={store} /> | ||||
|                 <ApiDescription description={store.spec.info.description} /> | ||||
|                 <ContentItems items={menu.items as any} /> | ||||
|               </ApiContentWrap> | ||||
|               <BackgroundStub /> | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import { MenuItem } from '../SideMenu/MenuItem'; | |||
| import { MarkerService } from '../../services/MarkerService'; | ||||
| import { SearchResult } from '../../services/SearchWorker.worker'; | ||||
| 
 | ||||
| import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar'; | ||||
| import { | ||||
|   ClearIcon, | ||||
|   SearchIcon, | ||||
|  | @ -135,6 +136,11 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat | |||
|           onChange={this.search} | ||||
|         /> | ||||
|         {results.length > 0 && ( | ||||
|           <PerfectScrollbarWrap | ||||
|             options={{ | ||||
|               wheelPropagation: false, | ||||
|             }} | ||||
|           > | ||||
|             <SearchResultsBox data-role="search:results"> | ||||
|               {results.map((res, idx) => ( | ||||
|                 <MenuItem | ||||
|  | @ -150,6 +156,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat | |||
|                 /> | ||||
|               ))} | ||||
|             </SearchResultsBox> | ||||
|           </PerfectScrollbarWrap> | ||||
|         )} | ||||
|       </SearchWrap> | ||||
|     ); | ||||
|  |  | |||
|  | @ -58,7 +58,6 @@ export const SearchResultsBox = styled.div` | |||
|   margin-top: 10px; | ||||
|   line-height: 1.4; | ||||
|   font-size: 0.9em; | ||||
|   overflow: auto; | ||||
| 
 | ||||
|   ${MenuItemLabel} { | ||||
|     padding-top: 6px; | ||||
|  |  | |||
|  | @ -82,20 +82,23 @@ export class SecurityRequirement extends React.PureComponent<SecurityRequirement | |||
|   } | ||||
| } | ||||
| 
 | ||||
| const AuthHeaderColumn = styled.td``; | ||||
| const AuthHeaderColumn = styled.div` | ||||
|   flex: 1; | ||||
| `;
 | ||||
| 
 | ||||
| const SecuritiesColumn = styled.td` | ||||
| const SecuritiesColumn = styled.div` | ||||
|   width: ${props => props.theme.schema.defaultDetailsWidth}; | ||||
| `;
 | ||||
| 
 | ||||
| const AuthHeader = UnderlinedHeader.extend` | ||||
|   display: inline-block; | ||||
|   margin: 0; | ||||
| `;
 | ||||
| 
 | ||||
| const Table = styled.table` | ||||
| const Wrap = styled.div` | ||||
|   width: 100%; | ||||
|   border-collapse: collapse; | ||||
|   font-size: inherit; | ||||
|   display: flex; | ||||
|   margin: 1em 0; | ||||
| `;
 | ||||
| 
 | ||||
| export interface SecurityRequirementsProps { | ||||
|  | @ -109,20 +112,14 @@ export class SecurityRequirements extends React.PureComponent<SecurityRequiremen | |||
|       return null; | ||||
|     } | ||||
|     return ( | ||||
|       <Table> | ||||
|         <tbody> | ||||
|           <tr> | ||||
|       <Wrap> | ||||
|         <AuthHeaderColumn> | ||||
|           <AuthHeader>Authorizations: </AuthHeader> | ||||
|         </AuthHeaderColumn> | ||||
|         <SecuritiesColumn> | ||||
|               {securities.map((security, idx) => ( | ||||
|                 <SecurityRequirement key={idx} security={security} /> | ||||
|               ))} | ||||
|           {securities.map((security, idx) => <SecurityRequirement key={idx} security={security} />)} | ||||
|         </SecuritiesColumn> | ||||
|           </tr> | ||||
|         </tbody> | ||||
|       </Table> | ||||
|       </Wrap> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import * as React from 'react'; | |||
| 
 | ||||
| import { SecuritySchemesModel } from '../../services/models'; | ||||
| 
 | ||||
| import { H2, ShareLink } from '../../common-elements'; | ||||
| import { H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements'; | ||||
| import { OpenAPISecurityScheme } from '../../types'; | ||||
| import { Markdown } from '../Markdown/Markdown'; | ||||
| import { StyledMarkdownBlock } from '../Markdown/styled.elements'; | ||||
|  | @ -66,10 +66,10 @@ export interface SecurityDefsProps { | |||
| 
 | ||||
| export class SecurityDefs extends React.PureComponent<SecurityDefsProps> { | ||||
|   render() { | ||||
|     return ( | ||||
|       <div> | ||||
|         {this.props.securitySchemes.schemes.map(scheme => ( | ||||
|           <div data-section-id={scheme.sectionId} key={scheme.id}> | ||||
|     return this.props.securitySchemes.schemes.map(scheme => ( | ||||
|       <Section id={scheme.sectionId} key={scheme.id}> | ||||
|         <Row> | ||||
|           <MiddlePanel> | ||||
|             <H2> | ||||
|               <ShareLink href={'#' + scheme.sectionId} /> | ||||
|               {scheme.id} | ||||
|  | @ -118,9 +118,9 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> { | |||
|                 </tbody> | ||||
|               </table> | ||||
|             </StyledMarkdownBlock> | ||||
|           </div> | ||||
|         ))} | ||||
|       </div> | ||||
|     ); | ||||
|           </MiddlePanel> | ||||
|         </Row> | ||||
|       </Section> | ||||
|     )); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,11 +1,10 @@ | |||
| import { observer } from 'mobx-react'; | ||||
| import * as React from 'react'; | ||||
| import { OptionsContext } from '../OptionsProvider'; | ||||
| 
 | ||||
| import { IMenuItem, MenuStore } from '../../services/MenuStore'; | ||||
| import { MenuItems } from './MenuItems'; | ||||
| 
 | ||||
| import { PerfectScrollbar } from '../../common-elements/perfect-scrollbar'; | ||||
| import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar'; | ||||
| import { RedocAttribution } from './styled.elements'; | ||||
| 
 | ||||
| @observer | ||||
|  | @ -15,31 +14,20 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str | |||
|   render() { | ||||
|     const store = this.props.menu; | ||||
|     return ( | ||||
|       <OptionsContext.Consumer> | ||||
|         {options => | ||||
|           options.nativeScrollbars ? ( | ||||
|             <MenuItems | ||||
|       <PerfectScrollbarWrap | ||||
|         updateFn={this.saveScrollUpdate} | ||||
|         className={this.props.className} | ||||
|               style={{ | ||||
|                 overflow: 'auto', | ||||
|                 msOverflowStyle: '-ms-autohiding-scrollbar', | ||||
|         options={{ | ||||
|           wheelPropagation: false, | ||||
|         }} | ||||
|               items={store.items} | ||||
|               onActivate={this.activate} | ||||
|               root={true} | ||||
|             /> | ||||
|           ) : ( | ||||
|             <PerfectScrollbar updateFn={this.saveScrollUpdate} className={this.props.className}> | ||||
|       > | ||||
|         <MenuItems items={store.items} onActivate={this.activate} root={true} /> | ||||
|         <RedocAttribution> | ||||
|           <a target="_blank" href="https://github.com/Rebilly/ReDoc"> | ||||
|             Documentation Powered by ReDoc | ||||
|           </a> | ||||
|         </RedocAttribution> | ||||
|             </PerfectScrollbar> | ||||
|           ) | ||||
|         } | ||||
|       </OptionsContext.Consumer> | ||||
|       </PerfectScrollbarWrap> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -164,9 +164,10 @@ export const MenuItemTitle = withProps<{ width?: string }>(styled.span)` | |||
| `;
 | ||||
| 
 | ||||
| export const RedocAttribution = styled.div` | ||||
|   ${({ theme }) => ` | ||||
|   font-size: 0.8em; | ||||
|   margin-top: ${({ theme }) => `${theme.spacing.unit * 2}px`}; | ||||
|   padding: ${({ theme }) => `0 ${theme.spacing.unit * 4}px`}; | ||||
|   margin-top: ${theme.spacing.unit * 2}px; | ||||
|   padding: 0 ${theme.spacing.unit * 4}px; | ||||
|   text-align: left; | ||||
| 
 | ||||
|   opacity: 0.7; | ||||
|  | @ -174,9 +175,10 @@ export const RedocAttribution = styled.div` | |||
|   a, | ||||
|   a:visited, | ||||
|   a:hover { | ||||
|     color: ${({ theme }) => theme.colors.text.primary} !important; | ||||
|     color: ${theme.colors.text.primary} !important; | ||||
|     border-top: 1px solid #e1e1e1; | ||||
|     padding-top: 10px; | ||||
|     padding: ${theme.spacing.unit}px 0; | ||||
|     display: block; | ||||
|   } | ||||
| `};
 | ||||
| `;
 | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| export * from './RedocStandalone'; | ||||
| export * from './Redoc/Redoc'; | ||||
| export * from './ApiInfo/ApiInfo'; | ||||
| export * from './ApiInfo/ApiDescription'; | ||||
| export * from './ApiLogo/ApiLogo'; | ||||
| export * from './ContentItems/ContentItems'; | ||||
| export { ApiContentWrap, BackgroundStub, RedocWrap } from './Redoc/styled.elements'; | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| export * from './components'; | ||||
| export { MiddlePanel, Row, RightPanel } from './common-elements/'; | ||||
| export { MiddlePanel, Row, RightPanel, Section } from './common-elements/'; | ||||
| export * from './services'; | ||||
| export * from './utils'; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user