feat: Add MDX Slate style

This commit is contained in:
Till Kolter 2020-08-23 22:44:18 +02:00
parent 029b82b209
commit 49f04d321f
21 changed files with 461 additions and 69 deletions

View File

@ -1,4 +1,9 @@
{ {
"editor.formatOnSave": true, "editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib" "typescript.tsdk": "node_modules/typescript/lib",
"[typescript]": {
"editor.codeActionsOnSave": {
"source.organizeImports": false
}
},
} }

View File

@ -27,15 +27,13 @@ const specUrl =
let store; let store;
const options: RedocRawOptions = { const options: RedocRawOptions = {
nativeScrollbars: false,
maxDisplayedEnumValues: 3,
expandAllSchemaFields: true, expandAllSchemaFields: true,
hideHttpVerbs: true,
hideShelfIcon: true, hideShelfIcon: true,
hideHttpVerbs: true maxDisplayedEnumValues: 3,
nativeScrollbars: false,
}; };
console.log('options', options)
async function init() { async function init() {
const spec = await loadAndBundleSpec(specUrl); const spec = await loadAndBundleSpec(specUrl);
store = new AppStore(spec, specUrl, options); store = new AppStore(spec, specUrl, options);

34
package-lock.json generated
View File

@ -1973,8 +1973,7 @@
"@mdx-js/react": { "@mdx-js/react": {
"version": "1.6.16", "version": "1.6.16",
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.16.tgz", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.16.tgz",
"integrity": "sha512-+FhuSVOPo7+4fZaRwWuCSRUcZkJOkZu0rfAbBKvoCg1LWb1Td8Vzi0DTLORdSvgWNbU6+EL40HIgwTOs00x2Jw==", "integrity": "sha512-+FhuSVOPo7+4fZaRwWuCSRUcZkJOkZu0rfAbBKvoCg1LWb1Td8Vzi0DTLORdSvgWNbU6+EL40HIgwTOs00x2Jw=="
"dev": true
}, },
"@mdx-js/util": { "@mdx-js/util": {
"version": "1.6.16", "version": "1.6.16",
@ -9098,6 +9097,20 @@
"property-information": "^5.0.0", "property-information": "^5.0.0",
"vfile": "^4.0.0", "vfile": "^4.0.0",
"web-namespaces": "^1.0.0" "web-namespaces": "^1.0.0"
},
"dependencies": {
"hastscript": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz",
"integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==",
"dev": true,
"requires": {
"comma-separated-tokens": "^1.0.0",
"hast-util-parse-selector": "^2.0.0",
"property-information": "^5.0.0",
"space-separated-tokens": "^1.0.0"
}
}
} }
}, },
"hast-util-is-element": { "hast-util-is-element": {
@ -9175,18 +9188,6 @@
"integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==", "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==",
"dev": true "dev": true
}, },
"hastscript": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz",
"integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==",
"dev": true,
"requires": {
"comma-separated-tokens": "^1.0.0",
"hast-util-parse-selector": "^2.0.0",
"property-information": "^5.0.0",
"space-separated-tokens": "^1.0.0"
}
},
"he": { "he": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@ -15192,6 +15193,11 @@
} }
} }
}, },
"prism-react-renderer": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.1.1.tgz",
"integrity": "sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug=="
},
"prismjs": { "prismjs": {
"version": "1.20.0", "version": "1.20.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz",

View File

@ -140,6 +140,7 @@
"styled-components": "^4.1.1" "styled-components": "^4.1.1"
}, },
"dependencies": { "dependencies": {
"@mdx-js/react": "^1.6.16",
"@types/node": "^13.11.1", "@types/node": "^13.11.1",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"decko": "^1.2.0", "decko": "^1.2.0",
@ -155,6 +156,7 @@
"openapi-sampler": "^1.0.0-beta.16", "openapi-sampler": "^1.0.0-beta.16",
"perfect-scrollbar": "^1.4.0", "perfect-scrollbar": "^1.4.0",
"polished": "^3.6.5", "polished": "^3.6.5",
"prism-react-renderer": "^1.1.1",
"prismjs": "^1.20.0", "prismjs": "^1.20.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react-dropdown-aria": "^2.0.7", "react-dropdown-aria": "^2.0.7",

View File

@ -0,0 +1,15 @@
import styled from 'styled-components'
export const Flex = styled.div`
display: flex;
justify-content: ${(props) => props.justifyContent};
width: 100%;
`;
export const HFlex = styled(Flex)`
flex-direction: row;
`;
export const VFlex = styled(Flex)`
flex-direction: column;
`;

View File

@ -17,7 +17,9 @@ export class ContentItems extends React.Component<{
if (items.length === 0) { if (items.length === 0) {
return null; return null;
} }
return items.map(item => { return items
.filter((item) => item.type !== 'extra')
.map((item) => {
return <ContentItem key={item.id} item={item} />; return <ContentItem key={item.id} item={item} />;
}); });
} }
@ -61,7 +63,7 @@ export class ContentItem extends React.Component<ContentItemProps> {
} }
} }
const middlePanelWrap = component => <MiddlePanel compact={true}>{component}</MiddlePanel>; const middlePanelWrap = (component) => <MiddlePanel compact={true}>{component}</MiddlePanel>;
@observer @observer
export class SectionItem extends React.Component<ContentItemProps> { export class SectionItem extends React.Component<ContentItemProps> {

View File

@ -1,5 +1,6 @@
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import * as React from 'react'; import * as React from 'react';
import { MDXProvider } from '@mdx-js/react';
import { ThemeProvider } from '../../styled-components'; import { ThemeProvider } from '../../styled-components';
import { OptionsProvider } from '../OptionsProvider'; import { OptionsProvider } from '../OptionsProvider';
@ -15,6 +16,8 @@ import { ApiContentWrap, BackgroundStub, RedocWrap } from './styled.elements';
import { SearchBox } from '../SearchBox/SearchBox'; import { SearchBox } from '../SearchBox/SearchBox';
import { StoreProvider } from '../StoreBuilder'; import { StoreProvider } from '../StoreBuilder';
import { sections, components } from '../../markdown';
export interface RedocProps { export interface RedocProps {
store: AppStore; store: AppStore;
} }
@ -41,6 +44,7 @@ export class Redoc extends React.Component<RedocProps> {
<ThemeProvider theme={options.theme}> <ThemeProvider theme={options.theme}>
<StoreProvider value={this.props.store}> <StoreProvider value={this.props.store}>
<OptionsProvider value={options}> <OptionsProvider value={options}>
<MDXProvider components={components}>
<RedocWrap className="redoc-wrap"> <RedocWrap className="redoc-wrap">
<StickyResponsiveSidebar menu={menu} className="menu-content"> <StickyResponsiveSidebar menu={menu} className="menu-content">
<ApiLogo info={spec.info} /> <ApiLogo info={spec.info} />
@ -57,10 +61,14 @@ export class Redoc extends React.Component<RedocProps> {
</StickyResponsiveSidebar> </StickyResponsiveSidebar>
<ApiContentWrap className="api-content"> <ApiContentWrap className="api-content">
<ApiInfo store={store} /> <ApiInfo store={store} />
{sections.map((MDXComponent, idx) => {
return <MDXComponent key={`section-${idx}`} />;
})}
<ContentItems items={menu.items as any} /> <ContentItems items={menu.items as any} />
</ApiContentWrap> </ApiContentWrap>
<BackgroundStub /> <BackgroundStub />
</RedocWrap> </RedocWrap>
</MDXProvider>
</OptionsProvider> </OptionsProvider>
</StoreProvider> </StoreProvider>
</ThemeProvider> </ThemeProvider>

View File

@ -2,6 +2,7 @@ import { observer } from 'mobx-react';
import * as React from 'react'; import * as React from 'react';
import { IMenuItem } from '../../services'; import { IMenuItem } from '../../services';
import { ExtraContent } from '../../services/models/ExtraContent';
import { MenuItem } from './MenuItem'; import { MenuItem } from './MenuItem';
import { MenuItemUl } from './styled.elements'; import { MenuItemUl } from './styled.elements';
@ -12,6 +13,7 @@ export interface MenuItemsProps {
onActivate?: (item: IMenuItem) => void; onActivate?: (item: IMenuItem) => void;
style?: React.CSSProperties; style?: React.CSSProperties;
root?: boolean; root?: boolean;
extra?: any;
className?: string; className?: string;
} }
@ -19,7 +21,7 @@ export interface MenuItemsProps {
@observer @observer
export class MenuItems extends React.Component<MenuItemsProps> { export class MenuItems extends React.Component<MenuItemsProps> {
render() { render() {
const { items, root, className } = this.props; const { items, root, className, extra } = this.props;
const expanded = this.props.expanded == null ? true : this.props.expanded; const expanded = this.props.expanded == null ? true : this.props.expanded;
return ( return (
<MenuItemUl <MenuItemUl
@ -28,6 +30,16 @@ export class MenuItems extends React.Component<MenuItemsProps> {
expanded={expanded} expanded={expanded}
{...(root ? { role: 'navigation' } : {})} {...(root ? { role: 'navigation' } : {})}
> >
{extra &&
extra.map((headline, ids) => (
<MenuItem
key={ids}
item={
new ExtraContent({ id: headline.id, name: headline.text, depth: headline.depth })
}
onActivate={this.props.onActivate}
/>
))}
{items.map((item, idx) => ( {items.map((item, idx) => (
<MenuItem key={idx} item={item} onActivate={this.props.onActivate} /> <MenuItem key={idx} item={item} onActivate={this.props.onActivate} />
))} ))}

View File

@ -9,7 +9,11 @@ import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
import { RedocAttribution } from './styled.elements'; import { RedocAttribution } from './styled.elements';
@observer @observer
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> { export class SideMenu extends React.Component<{
menu: MenuStore;
className?: string;
extra?: any;
}> {
static contextType = OptionsContext; static contextType = OptionsContext;
private _updateScroll?: () => void; private _updateScroll?: () => void;
@ -23,7 +27,12 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
wheelPropagation: false, wheelPropagation: false,
}} }}
> >
<MenuItems items={store.items} onActivate={this.activate} root={true} /> <MenuItems
items={store.items}
extra={this.props.extra}
onActivate={this.activate}
root={true}
/>
<RedocAttribution> <RedocAttribution>
<a target="_blank" rel="noopener noreferrer" href="https://github.com/Redocly/redoc"> <a target="_blank" rel="noopener noreferrer" href="https://github.com/Redocly/redoc">
Documentation Powered by ReDoc Documentation Powered by ReDoc
@ -46,7 +55,7 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
}); });
}; };
private saveScrollUpdate = upd => { private saveScrollUpdate = (upd) => {
this._updateScroll = upd; this._updateScroll = upd;
}; };
} }

View File

@ -1,6 +1,5 @@
import * as classnames from 'classnames'; import * as classnames from 'classnames';
import { darken } from 'polished'; import { darken } from 'polished';
import { deprecatedCss, ShelfIcon } from '../../common-elements'; import { deprecatedCss, ShelfIcon } from '../../common-elements';
import styled, { css, ResolvedThemeInterface } from '../../styled-components'; import styled, { css, ResolvedThemeInterface } from '../../styled-components';
@ -107,12 +106,17 @@ export const menuItemDepth = {
1: css` 1: css`
font-size: 0.929em; font-size: 0.929em;
text-transform: ${({ theme }) => theme.sidebar.level1Items.textTransform}; text-transform: ${({ theme }) => theme.sidebar.level1Items.textTransform};
font-weight: ${({ theme }) => theme.sidebar.level1Items.fw};
font-size: ${({ theme }) => theme.sidebar.level1Items.fontSize};
color: ${({theme}) => theme.sidebar.level1Items.color};
&:hover { &:hover {
color: ${props => props.theme.sidebar.activeTextColor}; color: ${props => props.theme.sidebar.activeTextColor};
} }
`, `,
2: css` 2: css`
color: ${props => props.theme.sidebar.textColor}; color: ${({ theme }) => theme.sidebar.level1Items.color};
font-size: ${({ theme }) => theme.sidebar.level2Items.fontSize};
`, `,
}; };
@ -133,7 +137,7 @@ export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
color: ${props => color: ${props =>
props.active ? props.theme.sidebar.activeTextColor : props.theme.sidebar.textColor}; props.active ? props.theme.sidebar.activeTextColor : props.theme.sidebar.textColor};
margin: 0; margin: 0;
padding: 12.5px ${props => props.theme.spacing.unit * 4}px; padding: 12.5px ${props => !props.depth || props.depth === 0 ? `${props.theme.spacing.unit * 2}px` : `${props.depth}rem`};
${({ depth, type, theme }) => ${({ depth, type, theme }) =>
(type === 'section' && depth > 1 && 'padding-left: ' + theme.spacing.unit * 8 + 'px;') || ''} (type === 'section' && depth > 1 && 'padding-left: ' + theme.spacing.unit * 8 + 'px;') || ''}
display: flex; display: flex;

View File

@ -0,0 +1,21 @@
import * as React from 'react';
import { HFlex } from '../common-elements/flex';
import styled from 'styled-components';
const Button = styled.button`
margin-left: auto;
`;
const TryOutWidget: React.FC = () => {
const onClick = (e) => {
e.preventDefault();
window.location.href = 'https://en.wikipedia.org/wiki/Register';
};
return (
<HFlex>
<Button onClick={onClick}>TRY OUT</Button>
</HFlex>
);
};
export default TryOutWidget;

55
src/markdown/auth.mdx Normal file
View File

@ -0,0 +1,55 @@
# Authentication
```bash
#!/bin/bash
BSDEX_API_KEY=put-your-api-key-here
BSDEX_API_SECRET=put-your-api-secret-here
DATE="`date -u '+%a, %d %b %Y %T %Z'`"
# The signature is in fact HMAC signature of a few fields, currently only "date", using your API secret.
AUTHORIZATION="hmac username=\"$BSDEX_API_KEY\", algorithm=\"hmac-sha1\", headers=\"date\", signature=\"`/bin/echo -n "date: ${DATE}" | openssl sha1 -binary -hmac "${BSDEX_API_SECRET}" | base64 `\""
curl -v \
-H "Date: $DATE" \
-H "ApiKey: $BSDEX_API_KEY" \
-H "Authorization: $AUTHORIZATION" \
-X GET "https://api-public.prelive.cex.tribe.sh/api/v1/balance"
```
> And you'll receive a response such as:
```json
[
{
"asset_id": "btc",
"available": "0",
"locked": "0"
},
{
"asset_id": "eur",
"available": "34163",
"locked": "123"
}
]
```
The requests are secured via Keyed-Hashing for Message Authentication (HMAC).
In order to authenticate, you need to add the following headers in your HTTP requests in addition to the `Authentication` header:
| Header | Description | Example |
| ------ | ----------- | ------- |
|`Date`| Current date in RFC7231 format | Wed, 12 Aug 2020 12:49:26 UTC |
|`ApiKey`| Your API key | put-your-api-key-here |
The `Authentication` header's value has the pattern
`hmac username=<ApiKey>, algorithm="hmac-sha1", headers="date", signature="<signed_date>"`
where `signed_date` is the base64-encoded HMAC-SHA1 signature for the expression `date: <Date>`.
Have a look at the code example to understand the HMAC signing process better.
<aside class="notice">
It is important to note that the value of the <em>Date</em> header must match the signed date value.
</aside>

View File

@ -0,0 +1,37 @@
import * as React from 'react';
import Highlight, { defaultProps, Language } from 'prism-react-renderer';
import styled from 'styled-components';
type Props = {
className: string;
};
const OverflowHighlighter = styled.pre`
overflow-x: scroll;
`;
const Highligher: React.FC<Props> = ({ children, className }) => {
const language = className.replace(/language-/, '');
if (!children) {
return null;
}
return (
<Highlight {...defaultProps} code={children.toString()} language={language as Language}>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<OverflowHighlighter className={className} style={{ ...style, padding: '20px' }}>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({ line, key: i })}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token, key })} />
))}
</div>
))}
</OverflowHighlighter>
)}
</Highlight>
);
};
export default Highligher;

100
src/markdown/index.tsx Normal file
View File

@ -0,0 +1,100 @@
import * as React from 'react';
import {
DarkRightPanel,
H1,
H2,
MiddlePanel,
PropertiesTable,
Row,
Section,
ShareLink,
} from '../common-elements';
import Highlighter from './code/Highlighter';
const INCLUDES = ['auth', 'welcome'];
const H1Comp = ({ children, ...props }) => {
return (
<H1 id={props.id}>
<ShareLink to={props.id as string} />
{children}
</H1>
);
};
const H2Comp = ({ children, ...props }) => (
<H2 id={props.id}>
<ShareLink to={props.id as string} />
{children}
</H2>
);
const Wrapper = ({ children }) => {
const getSections = (children) => {
let currentSection: any[] = [];
let currentId: number | null = null;
let currentCodeBlocks: any[] = [];
const result: any[] = [];
children.forEach((child) => {
const type = child.props.mdxType;
if (['h1', 'h2', 'h3', 'h4', 'h5'].includes(type)) {
if (currentSection.length > 0) {
result.push({
id: currentId,
middle: currentSection,
right: currentCodeBlocks,
});
}
currentId = child.props.id;
currentSection = [];
currentCodeBlocks = [];
}
if (['code', 'pre', 'blockquote'].includes(type)) {
currentCodeBlocks.push(child);
} else {
currentSection.push(child);
}
});
if (currentSection.length > 0) {
result.push({ id: currentId, middle: currentSection, right: currentCodeBlocks });
}
return result;
};
const sections = getSections(children);
return (
<>
{sections.map((section) => (
<Section key={`section-${section.id}`} id={section.id}>
<Row id={section.id}>
{section.middle && <MiddlePanel>{section.middle}</MiddlePanel>}
{section.right && <DarkRightPanel>{section.right}</DarkRightPanel>}
</Row>
</Section>
))}
</>
);
};
const includedSections = INCLUDES.map((include) => require(`./${include}.mdx`));
export const sections = includedSections.map((sect) => sect.default);
export const headings = includedSections.reduce(
(acc, section) => [...acc, ...section.headings],
[],
);
export const components = {
code: Highlighter,
h1: H1Comp,
h2: H2Comp,
table: PropertiesTable,
wrapper: Wrapper,
};
export default sections;

View File

@ -0,0 +1,38 @@
'use strict';
const visit = require('unist-util-visit');
module.exports = slug;
function slug() {
return transformer;
}
// Patch slugs on heading nodes.
function transformer(ast) {
const headlines: any[] = [];
visit(ast, 'heading', visitor);
function visitor(node) {
const data = node.data || (node.data = {});
const props = data.hProperties || (data.hProperties = {});
const sectionId = `section/${data.id}`;
data.id = sectionId;
props.id = sectionId;
visit(node, 'text', (textNode) => {
headlines.push({ depth: node.depth, id: data.id, text: textNode.value });
});
}
const value = `export const headings = ${JSON.stringify(headlines)};`;
const meta = {
default: false,
type: 'export',
value,
};
ast.children.splice(0, 0, meta);
}

11
src/markdown/welcome.mdx Normal file
View File

@ -0,0 +1,11 @@
# Hello, world!
First intro
## Hello subline
Some text
import TryOutWidget from './TryOutWidget';
<TryOutWidget />

View File

@ -89,7 +89,7 @@ export class AppStore {
this.search.indexItems(this.menu.items); this.search.indexItems(this.menu.items);
} }
this.disposer = observe(this.menu, 'activeItemIdx', change => { this.disposer = observe(this.menu, 'activeItemIdx', (change) => {
this.updateMarkOnMenu(change.newValue as number); this.updateMarkOnMenu(change.newValue as number);
}); });
} }

View File

@ -1,14 +1,14 @@
import { action, observable } from 'mobx'; import { action, observable } from 'mobx';
import { querySelector } from '../utils/dom';
import { SpecStore } from './models';
import { history as historyInst, HistoryService } from './HistoryService';
import { ScrollService } from './ScrollService';
import { flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils'; import { flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
import { querySelector } from '../utils/dom';
import { history as historyInst, HistoryService } from './HistoryService';
import { GROUP_DEPTH } from './MenuBuilder'; import { GROUP_DEPTH } from './MenuBuilder';
import { SpecStore } from './models';
import { ScrollService } from './ScrollService';
import { ExtraContent } from './models/ExtraContent';
import { headings } from '../markdown';
export type MenuItemGroupType = 'group' | 'tag' | 'section'; export type MenuItemGroupType = 'group' | 'tag' | 'section' | 'extra';
export type MenuItemType = MenuItemGroupType | 'operation'; export type MenuItemType = MenuItemGroupType | 'operation';
/** Generic interface for MenuItems */ /** Generic interface for MenuItems */
@ -76,7 +76,15 @@ export class MenuStore {
* @param scroll scroll service instance used by this menu * @param scroll scroll service instance used by this menu
*/ */
constructor(spec: SpecStore, public scroll: ScrollService, public history: HistoryService) { constructor(spec: SpecStore, public scroll: ScrollService, public history: HistoryService) {
this.items = spec.contentItems; const extraItems = headings.map(
({ id, text, depth }) =>
new ExtraContent({
id,
name: text,
depth,
}),
);
this.items = [...extraItems, ...spec.contentItems];
this.flatItems = flattenByProp(this.items || [], 'items'); this.flatItems = flattenByProp(this.items || [], 'items');
this.flatItems.forEach((item, idx) => (item.absoluteIdx = idx)); this.flatItems.forEach((item, idx) => (item.absoluteIdx = idx));
@ -142,12 +150,12 @@ export class MenuStore {
} }
let item: IMenuItem | undefined; let item: IMenuItem | undefined;
item = this.flatItems.find(i => i.id === id); item = this.flatItems.find((i) => i.id === id);
if (item) { if (item) {
this.activateAndScroll(item, false); this.activateAndScroll(item, false);
} else { } else {
if (id.startsWith(SECURITY_SCHEMES_SECTION_PREFIX)) { if (id.startsWith(SECURITY_SCHEMES_SECTION_PREFIX)) {
item = this.flatItems.find(i => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id)); item = this.flatItems.find((i) => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id));
this.activate(item); this.activate(item);
} }
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`); this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
@ -183,7 +191,7 @@ export class MenuStore {
} }
getItemById = (id: string) => { getItemById = (id: string) => {
return this.flatItems.find(item => item.id === id); return this.flatItems.find((item) => item.id === id);
}; };
/** /**

View File

@ -0,0 +1,58 @@
import { action, observable } from 'mobx';
import { ContentItemModel } from '..';
import { OpenAPIExternalDocumentation } from '../../types';
import { IMenuItem } from '../MenuStore';
export class ExtraContent implements IMenuItem {
//#region IMenuItem fields
id: string;
absoluteIdx?: number;
name: string;
description?: string;
type = 'extra' as const;
content: JSX.Element
items: ContentItemModel[] = [];
parent?: ExtraContent;
externalDocs?: OpenAPIExternalDocumentation;
@observable
active: boolean = false;
@observable
expanded: boolean = false;
depth: number;
level: number;
constructor({ id, name, depth }) {
this.id = id
this.name = name
this.depth = depth
}
/**
* set operation as active (used by side menu)
*/
@action
activate() {
this.active = true;
}
/**
* set operation as inactive (used by side menu)
*/
@action
deactivate() {
this.active = false;
}
expand() {
if (this.parent) {
this.parent.expand();
}
}
collapse() {
/* do nothing */
}
}

View File

@ -1,13 +1,15 @@
export * from '../SpecStore'; export * from '../SpecStore';
export * from './Group.model'; export * from './ApiInfo';
export * from './Operation'; export * from './Callback';
export * from './RequestBody';
export * from './Example'; export * from './Example';
export * from './ExtraContent';
export * from './Field';
export * from './Group.model';
export * from './MediaContent'; export * from './MediaContent';
export * from './MediaType'; export * from './MediaType';
export * from './Operation';
export * from './RequestBody';
export * from './Response'; export * from './Response';
export * from './Schema'; export * from './Schema';
export * from './Field';
export * from './ApiInfo';
export * from './SecuritySchemes'; export * from './SecuritySchemes';
export * from './Callback';

1
src/types/mdx.d.ts vendored
View File

@ -1,4 +1,5 @@
declare module '*.mdx' { declare module '*.mdx' {
let MDXComponent: (props: any) => JSX.Element; let MDXComponent: (props: any) => JSX.Element;
export const headings: any;
export default MDXComponent; export default MDXComponent;
} }