mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-07 13:44:54 +03:00
feat: Add MDX Slate style
This commit is contained in:
parent
029b82b209
commit
49f04d321f
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
|
@ -1,4 +1,9 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"[typescript]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": false
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -27,15 +27,13 @@ const specUrl =
|
|||
|
||||
let store;
|
||||
const options: RedocRawOptions = {
|
||||
nativeScrollbars: false,
|
||||
maxDisplayedEnumValues: 3,
|
||||
expandAllSchemaFields: true,
|
||||
hideHttpVerbs: true,
|
||||
hideShelfIcon: true,
|
||||
hideHttpVerbs: true
|
||||
maxDisplayedEnumValues: 3,
|
||||
nativeScrollbars: false,
|
||||
};
|
||||
|
||||
console.log('options', options)
|
||||
|
||||
async function init() {
|
||||
const spec = await loadAndBundleSpec(specUrl);
|
||||
store = new AppStore(spec, specUrl, options);
|
||||
|
|
34
package-lock.json
generated
34
package-lock.json
generated
|
@ -1973,8 +1973,7 @@
|
|||
"@mdx-js/react": {
|
||||
"version": "1.6.16",
|
||||
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.16.tgz",
|
||||
"integrity": "sha512-+FhuSVOPo7+4fZaRwWuCSRUcZkJOkZu0rfAbBKvoCg1LWb1Td8Vzi0DTLORdSvgWNbU6+EL40HIgwTOs00x2Jw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-+FhuSVOPo7+4fZaRwWuCSRUcZkJOkZu0rfAbBKvoCg1LWb1Td8Vzi0DTLORdSvgWNbU6+EL40HIgwTOs00x2Jw=="
|
||||
},
|
||||
"@mdx-js/util": {
|
||||
"version": "1.6.16",
|
||||
|
@ -9098,6 +9097,20 @@
|
|||
"property-information": "^5.0.0",
|
||||
"vfile": "^4.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": {
|
||||
|
@ -9175,18 +9188,6 @@
|
|||
"integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==",
|
||||
"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": {
|
||||
"version": "1.2.0",
|
||||
"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": {
|
||||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz",
|
||||
|
|
|
@ -140,6 +140,7 @@
|
|||
"styled-components": "^4.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdx-js/react": "^1.6.16",
|
||||
"@types/node": "^13.11.1",
|
||||
"classnames": "^2.2.6",
|
||||
"decko": "^1.2.0",
|
||||
|
@ -155,6 +156,7 @@
|
|||
"openapi-sampler": "^1.0.0-beta.16",
|
||||
"perfect-scrollbar": "^1.4.0",
|
||||
"polished": "^3.6.5",
|
||||
"prism-react-renderer": "^1.1.1",
|
||||
"prismjs": "^1.20.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-dropdown-aria": "^2.0.7",
|
||||
|
|
15
src/common-elements/flex.ts
Normal file
15
src/common-elements/flex.ts
Normal 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;
|
||||
`;
|
|
@ -17,9 +17,11 @@ export class ContentItems extends React.Component<{
|
|||
if (items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return items.map(item => {
|
||||
return <ContentItem key={item.id} item={item} />;
|
||||
});
|
||||
return items
|
||||
.filter((item) => item.type !== 'extra')
|
||||
.map((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
|
||||
export class SectionItem extends React.Component<ContentItemProps> {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as PropTypes from 'prop-types';
|
||||
import * as React from 'react';
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
|
||||
import { ThemeProvider } from '../../styled-components';
|
||||
import { OptionsProvider } from '../OptionsProvider';
|
||||
|
@ -15,6 +16,8 @@ import { ApiContentWrap, BackgroundStub, RedocWrap } from './styled.elements';
|
|||
import { SearchBox } from '../SearchBox/SearchBox';
|
||||
import { StoreProvider } from '../StoreBuilder';
|
||||
|
||||
import { sections, components } from '../../markdown';
|
||||
|
||||
export interface RedocProps {
|
||||
store: AppStore;
|
||||
}
|
||||
|
@ -41,26 +44,31 @@ export class Redoc extends React.Component<RedocProps> {
|
|||
<ThemeProvider theme={options.theme}>
|
||||
<StoreProvider value={this.props.store}>
|
||||
<OptionsProvider value={options}>
|
||||
<RedocWrap className="redoc-wrap">
|
||||
<StickyResponsiveSidebar menu={menu} className="menu-content">
|
||||
<ApiLogo info={spec.info} />
|
||||
{(!options.disableSearch && (
|
||||
<SearchBox
|
||||
search={search!}
|
||||
marker={marker}
|
||||
getItemById={menu.getItemById}
|
||||
onActivate={menu.activateAndScroll}
|
||||
/>
|
||||
)) ||
|
||||
null}
|
||||
<SideMenu menu={menu} />
|
||||
</StickyResponsiveSidebar>
|
||||
<ApiContentWrap className="api-content">
|
||||
<ApiInfo store={store} />
|
||||
<ContentItems items={menu.items as any} />
|
||||
</ApiContentWrap>
|
||||
<BackgroundStub />
|
||||
</RedocWrap>
|
||||
<MDXProvider components={components}>
|
||||
<RedocWrap className="redoc-wrap">
|
||||
<StickyResponsiveSidebar menu={menu} className="menu-content">
|
||||
<ApiLogo info={spec.info} />
|
||||
{(!options.disableSearch && (
|
||||
<SearchBox
|
||||
search={search!}
|
||||
marker={marker}
|
||||
getItemById={menu.getItemById}
|
||||
onActivate={menu.activateAndScroll}
|
||||
/>
|
||||
)) ||
|
||||
null}
|
||||
<SideMenu menu={menu} />
|
||||
</StickyResponsiveSidebar>
|
||||
<ApiContentWrap className="api-content">
|
||||
<ApiInfo store={store} />
|
||||
{sections.map((MDXComponent, idx) => {
|
||||
return <MDXComponent key={`section-${idx}`} />;
|
||||
})}
|
||||
<ContentItems items={menu.items as any} />
|
||||
</ApiContentWrap>
|
||||
<BackgroundStub />
|
||||
</RedocWrap>
|
||||
</MDXProvider>
|
||||
</OptionsProvider>
|
||||
</StoreProvider>
|
||||
</ThemeProvider>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { observer } from 'mobx-react';
|
|||
import * as React from 'react';
|
||||
|
||||
import { IMenuItem } from '../../services';
|
||||
import { ExtraContent } from '../../services/models/ExtraContent';
|
||||
|
||||
import { MenuItem } from './MenuItem';
|
||||
import { MenuItemUl } from './styled.elements';
|
||||
|
@ -12,6 +13,7 @@ export interface MenuItemsProps {
|
|||
onActivate?: (item: IMenuItem) => void;
|
||||
style?: React.CSSProperties;
|
||||
root?: boolean;
|
||||
extra?: any;
|
||||
|
||||
className?: string;
|
||||
}
|
||||
|
@ -19,7 +21,7 @@ export interface MenuItemsProps {
|
|||
@observer
|
||||
export class MenuItems extends React.Component<MenuItemsProps> {
|
||||
render() {
|
||||
const { items, root, className } = this.props;
|
||||
const { items, root, className, extra } = this.props;
|
||||
const expanded = this.props.expanded == null ? true : this.props.expanded;
|
||||
return (
|
||||
<MenuItemUl
|
||||
|
@ -28,6 +30,16 @@ export class MenuItems extends React.Component<MenuItemsProps> {
|
|||
expanded={expanded}
|
||||
{...(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) => (
|
||||
<MenuItem key={idx} item={item} onActivate={this.props.onActivate} />
|
||||
))}
|
||||
|
|
|
@ -9,7 +9,11 @@ import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
|||
import { RedocAttribution } from './styled.elements';
|
||||
|
||||
@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;
|
||||
private _updateScroll?: () => void;
|
||||
|
||||
|
@ -23,7 +27,12 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
|
|||
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>
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://github.com/Redocly/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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import * as classnames from 'classnames';
|
||||
import { darken } from 'polished';
|
||||
|
||||
import { deprecatedCss, ShelfIcon } from '../../common-elements';
|
||||
import styled, { css, ResolvedThemeInterface } from '../../styled-components';
|
||||
|
||||
|
@ -107,12 +106,17 @@ export const menuItemDepth = {
|
|||
1: css`
|
||||
font-size: 0.929em;
|
||||
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 {
|
||||
color: ${props => props.theme.sidebar.activeTextColor};
|
||||
}
|
||||
`,
|
||||
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 =>
|
||||
props.active ? props.theme.sidebar.activeTextColor : props.theme.sidebar.textColor};
|
||||
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 }) =>
|
||||
(type === 'section' && depth > 1 && 'padding-left: ' + theme.spacing.unit * 8 + 'px;') || ''}
|
||||
display: flex;
|
||||
|
|
21
src/markdown/TryOutWidget.tsx
Normal file
21
src/markdown/TryOutWidget.tsx
Normal 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
55
src/markdown/auth.mdx
Normal 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>
|
||||
|
37
src/markdown/code/Highlighter/index.tsx
Normal file
37
src/markdown/code/Highlighter/index.tsx
Normal 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
100
src/markdown/index.tsx
Normal 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;
|
38
src/markdown/plugins/export-toc.ts
Normal file
38
src/markdown/plugins/export-toc.ts
Normal 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
11
src/markdown/welcome.mdx
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Hello, world!
|
||||
|
||||
First intro
|
||||
|
||||
## Hello subline
|
||||
|
||||
Some text
|
||||
|
||||
import TryOutWidget from './TryOutWidget';
|
||||
|
||||
<TryOutWidget />
|
|
@ -89,7 +89,7 @@ export class AppStore {
|
|||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
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 { querySelector } from '../utils/dom';
|
||||
import { history as historyInst, HistoryService } from './HistoryService';
|
||||
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';
|
||||
|
||||
/** Generic interface for MenuItems */
|
||||
|
@ -76,7 +76,15 @@ export class MenuStore {
|
|||
* @param scroll scroll service instance used by this menu
|
||||
*/
|
||||
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.forEach((item, idx) => (item.absoluteIdx = idx));
|
||||
|
@ -142,12 +150,12 @@ export class MenuStore {
|
|||
}
|
||||
let item: IMenuItem | undefined;
|
||||
|
||||
item = this.flatItems.find(i => i.id === id);
|
||||
item = this.flatItems.find((i) => i.id === id);
|
||||
if (item) {
|
||||
this.activateAndScroll(item, false);
|
||||
} else {
|
||||
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.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
|
||||
|
@ -183,7 +191,7 @@ export class MenuStore {
|
|||
}
|
||||
|
||||
getItemById = (id: string) => {
|
||||
return this.flatItems.find(item => item.id === id);
|
||||
return this.flatItems.find((item) => item.id === id);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
58
src/services/models/ExtraContent.ts
Normal file
58
src/services/models/ExtraContent.ts
Normal 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 */
|
||||
}
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
export * from '../SpecStore';
|
||||
export * from './Group.model';
|
||||
export * from './Operation';
|
||||
export * from './RequestBody';
|
||||
export * from './ApiInfo';
|
||||
export * from './Callback';
|
||||
export * from './Example';
|
||||
export * from './ExtraContent';
|
||||
export * from './Field';
|
||||
export * from './Group.model';
|
||||
export * from './MediaContent';
|
||||
export * from './MediaType';
|
||||
export * from './Operation';
|
||||
export * from './RequestBody';
|
||||
export * from './Response';
|
||||
export * from './Schema';
|
||||
export * from './Field';
|
||||
export * from './ApiInfo';
|
||||
export * from './SecuritySchemes';
|
||||
export * from './Callback';
|
||||
|
||||
|
|
1
src/types/mdx.d.ts
vendored
1
src/types/mdx.d.ts
vendored
|
@ -1,4 +1,5 @@
|
|||
declare module '*.mdx' {
|
||||
let MDXComponent: (props: any) => JSX.Element;
|
||||
export const headings: any;
|
||||
export default MDXComponent;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user