@jpmorganchase/elemental-dev-portal 1.0.0 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- package/components/BranchSelector/BranchSelector.d.ts +7 -0
- package/components/BranchSelector/BranchSelector.spec.d.ts +15 -0
- package/components/BranchSelector/BranchSelector.stories.d.ts +29 -0
- package/components/DevPortalProvider/index.d.ts +7 -0
- package/components/Forbidden.d.ts +1 -0
- package/components/Loading.d.ts +1 -0
- package/components/NodeContent/NodeContent.d.ts +17 -0
- package/components/NodeContent/NodeContent.spec.d.ts +1 -0
- package/components/NodeContent/NodeContent.stories.d.ts +43 -0
- package/components/NotFound.d.ts +1 -0
- package/components/Search/Search.d.ts +13 -0
- package/components/Search/Search.stories.d.ts +33 -0
- package/components/TableOfContents/TableOfContents.d.ts +12 -0
- package/components/TableOfContents/TableOfContents.stories.d.ts +36 -0
- package/components/UpgradeToStarter.d.ts +1 -0
- package/consts.d.ts +28 -0
- package/containers/StoplightProject.d.ts +13 -0
- package/containers/StoplightProject.spec.d.ts +1 -0
- package/containers/StoplightProject.stories.d.ts +45 -0
- package/handlers/getBranches.d.ts +6 -0
- package/handlers/getNodeContent.d.ts +12 -0
- package/handlers/getNodes.d.ts +9 -0
- package/handlers/getTableOfContents.d.ts +7 -0
- package/handlers/getWorkspace.d.ts +6 -0
- package/hooks/useGetBranches.d.ts +3 -0
- package/hooks/useGetNodeContent.d.ts +5 -0
- package/hooks/useGetNodes.d.ts +7 -0
- package/hooks/useGetTableOfContents.d.ts +4 -0
- package/hooks/useGetWorkspace.d.ts +3 -0
- package/index.esm.js +682 -0
- package/index.js +726 -0
- package/index.mjs +682 -0
- package/package.json +17 -70
- package/styles.min.css +1 -0
- package/types.d.ts +65 -0
- package/version.d.ts +1 -0
- package/web-components/components.d.ts +1 -0
- package/web-components/index.d.ts +1 -0
- package/web-components.min.js +2 -0
- package/web-components.min.js.LICENSE.txt +187 -0
- package/.storybook/main.js +0 -1
- package/.storybook/manager.js +0 -1
- package/.storybook/preview.jsx +0 -46
- package/jest.config.js +0 -10
- package/src/__fixtures__/branches.json +0 -26
- package/src/__fixtures__/node-content.json +0 -257
- package/src/__fixtures__/table-of-contents.json +0 -144
- package/src/components/BranchSelector/BranchSelector.spec.tsx +0 -63
- package/src/components/BranchSelector/BranchSelector.stories.tsx +0 -41
- package/src/components/BranchSelector/BranchSelector.tsx +0 -50
- package/src/components/DevPortalProvider/index.tsx +0 -19
- package/src/components/Forbidden.tsx +0 -11
- package/src/components/Loading.tsx +0 -9
- package/src/components/NodeContent/NodeContent.spec.tsx +0 -54
- package/src/components/NodeContent/NodeContent.stories.tsx +0 -60
- package/src/components/NodeContent/NodeContent.tsx +0 -171
- package/src/components/NotFound.tsx +0 -11
- package/src/components/Search/Search.stories.tsx +0 -73
- package/src/components/Search/Search.tsx +0 -133
- package/src/components/TableOfContents/TableOfContents.stories.tsx +0 -54
- package/src/components/TableOfContents/TableOfContents.tsx +0 -51
- package/src/components/UpgradeToStarter.tsx +0 -22
- package/src/consts.ts +0 -32
- package/src/containers/StoplightProject.spec.tsx +0 -78
- package/src/containers/StoplightProject.stories.tsx +0 -28
- package/src/containers/StoplightProject.tsx +0 -213
- package/src/handlers/__tests__/getBranches.test.ts +0 -30
- package/src/handlers/__tests__/getNodeContent.test.ts +0 -35
- package/src/handlers/__tests__/getNodes.test.ts +0 -38
- package/src/handlers/__tests__/getTableOfContents.test.ts +0 -34
- package/src/handlers/__tests__/getWorkspace.test.ts +0 -30
- package/src/handlers/getBranches.ts +0 -27
- package/src/handlers/getNodeContent.ts +0 -53
- package/src/handlers/getNodes.ts +0 -69
- package/src/handlers/getTableOfContents.ts +0 -30
- package/src/handlers/getWorkspace.ts +0 -27
- package/src/hooks/__tests__/dataFetching.spec.tsx +0 -42
- package/src/hooks/useGetBranches.ts +0 -17
- package/src/hooks/useGetNodeContent.ts +0 -24
- package/src/hooks/useGetNodes.ts +0 -34
- package/src/hooks/useGetTableOfContents.ts +0 -15
- package/src/hooks/useGetWorkspace.tsx +0 -13
- package/src/styles.css +0 -1
- package/src/types.ts +0 -81
- package/src/version.ts +0 -2
- package/src/web-components/__stories__/StoplightProject.stories.tsx +0 -33
- package/src/web-components/components.ts +0 -17
- package/src/web-components/index.ts +0 -3
- package/tsconfig.build.json +0 -18
- package/tsconfig.json +0 -7
- package/web-components.config.js +0 -1
- /package/{src/components/BranchSelector/index.tsx → components/BranchSelector/index.d.ts} +0 -0
- /package/{src/components/NodeContent/index.tsx → components/NodeContent/index.d.ts} +0 -0
- /package/{src/components/Search/index.tsx → components/Search/index.d.ts} +0 -0
- /package/{src/components/TableOfContents/index.tsx → components/TableOfContents/index.d.ts} +0 -0
- /package/{src/index.ts → index.d.ts} +0 -0
@@ -1,133 +0,0 @@
|
|
1
|
-
import { faSearch, faSpinner } from '@fortawesome/free-solid-svg-icons';
|
2
|
-
import {
|
3
|
-
NodeTypeColors,
|
4
|
-
NodeTypeIconDefs,
|
5
|
-
withMosaicProvider,
|
6
|
-
withPersistenceBoundary,
|
7
|
-
withQueryClientProvider,
|
8
|
-
withStyles,
|
9
|
-
} from '@stoplight/elements-core';
|
10
|
-
import { Box, Flex, Icon, Input, ListBox, ListBoxItem, Modal, ModalProps } from '@stoplight/mosaic';
|
11
|
-
import { flow } from 'lodash';
|
12
|
-
import * as React from 'react';
|
13
|
-
|
14
|
-
import { NodeSearchResult } from '../../types';
|
15
|
-
|
16
|
-
export type SearchProps = {
|
17
|
-
isLoading?: boolean;
|
18
|
-
search?: string;
|
19
|
-
searchResults?: NodeSearchResult[];
|
20
|
-
onSearch: (search: string) => void;
|
21
|
-
onClick: (result: NodeSearchResult) => void;
|
22
|
-
isOpen?: boolean;
|
23
|
-
onClose: ModalProps['onClose'];
|
24
|
-
};
|
25
|
-
|
26
|
-
const SearchImpl = ({ isLoading, search, searchResults, isOpen, onClose, onClick, onSearch }: SearchProps) => {
|
27
|
-
const listBoxRef = React.useRef<HTMLDivElement>(null);
|
28
|
-
|
29
|
-
const onChange = React.useCallback(e => onSearch(e.currentTarget.value), [onSearch]);
|
30
|
-
|
31
|
-
const onKeyDown = React.useCallback(e => {
|
32
|
-
// Focus the search results on arrow down
|
33
|
-
if (e.key === 'ArrowDown') {
|
34
|
-
e.preventDefault();
|
35
|
-
listBoxRef.current?.focus();
|
36
|
-
}
|
37
|
-
}, []);
|
38
|
-
|
39
|
-
const onSelectionChange = React.useCallback(
|
40
|
-
keys => {
|
41
|
-
const selectedId = keys.values().next().value;
|
42
|
-
const selectedResult = searchResults?.find(
|
43
|
-
searchResult => `${searchResult.id}-${searchResult.project_id}` === selectedId,
|
44
|
-
);
|
45
|
-
if (selectedResult) {
|
46
|
-
onClick(selectedResult);
|
47
|
-
}
|
48
|
-
},
|
49
|
-
[searchResults, onClick],
|
50
|
-
);
|
51
|
-
|
52
|
-
return (
|
53
|
-
<Modal
|
54
|
-
renderHeader={() => (
|
55
|
-
<Input
|
56
|
-
appearance="minimal"
|
57
|
-
borderB
|
58
|
-
size="lg"
|
59
|
-
icon={<Box as={Icon} ml={1} icon={isLoading ? faSpinner : faSearch} spin={isLoading} />}
|
60
|
-
autoFocus
|
61
|
-
placeholder="Search..."
|
62
|
-
value={search}
|
63
|
-
onChange={onChange}
|
64
|
-
onKeyDown={onKeyDown}
|
65
|
-
/>
|
66
|
-
)}
|
67
|
-
isOpen={!!isOpen}
|
68
|
-
onClose={onClose}
|
69
|
-
>
|
70
|
-
{searchResults && searchResults.length > 0 ? (
|
71
|
-
<ListBox
|
72
|
-
ref={listBoxRef}
|
73
|
-
aria-label="Search"
|
74
|
-
overflowY="auto"
|
75
|
-
h={80}
|
76
|
-
m={-5}
|
77
|
-
items={searchResults}
|
78
|
-
selectionMode="single"
|
79
|
-
onSelectionChange={onSelectionChange}
|
80
|
-
>
|
81
|
-
{(searchResult: NodeSearchResult) => {
|
82
|
-
return (
|
83
|
-
<ListBoxItem key={`${searchResult.id}-${searchResult.project_id}`} textValue={searchResult.title}>
|
84
|
-
<Box p={3} borderB>
|
85
|
-
<Flex align="center">
|
86
|
-
<Box
|
87
|
-
as={Icon}
|
88
|
-
w={4}
|
89
|
-
icon={NodeTypeIconDefs[searchResult.type]}
|
90
|
-
style={{ color: NodeTypeColors[searchResult.type] }}
|
91
|
-
/>
|
92
|
-
|
93
|
-
<Box
|
94
|
-
flex={1}
|
95
|
-
fontSize="lg"
|
96
|
-
dangerouslySetInnerHTML={{ __html: searchResult.highlighted.name ?? '' }}
|
97
|
-
fontWeight="medium"
|
98
|
-
textOverflow="overflow-ellipsis"
|
99
|
-
mx={2}
|
100
|
-
/>
|
101
|
-
|
102
|
-
<Box fontSize="sm" color="muted">
|
103
|
-
{searchResult.project_name}
|
104
|
-
</Box>
|
105
|
-
</Flex>
|
106
|
-
|
107
|
-
<Box
|
108
|
-
dangerouslySetInnerHTML={{ __html: searchResult.highlighted.summary ?? '' }}
|
109
|
-
color="muted"
|
110
|
-
fontSize="sm"
|
111
|
-
mt={1}
|
112
|
-
ml={6}
|
113
|
-
/>
|
114
|
-
</Box>
|
115
|
-
</ListBoxItem>
|
116
|
-
);
|
117
|
-
}}
|
118
|
-
</ListBox>
|
119
|
-
) : (
|
120
|
-
<Flex w="full" h={80} align="center" justify="center" m={-5}>
|
121
|
-
No search results
|
122
|
-
</Flex>
|
123
|
-
)}
|
124
|
-
</Modal>
|
125
|
-
);
|
126
|
-
};
|
127
|
-
|
128
|
-
export const Search = flow(
|
129
|
-
withStyles,
|
130
|
-
withPersistenceBoundary,
|
131
|
-
withMosaicProvider,
|
132
|
-
withQueryClientProvider,
|
133
|
-
)(SearchImpl);
|
@@ -1,54 +0,0 @@
|
|
1
|
-
import { Story } from '@storybook/react';
|
2
|
-
import * as React from 'react';
|
3
|
-
|
4
|
-
import { useGetTableOfContents } from '../../hooks/useGetTableOfContents';
|
5
|
-
import { TableOfContents } from './';
|
6
|
-
|
7
|
-
// Wrapper to show how to use the node content hook
|
8
|
-
const TableOfContentsWrapper = ({ projectId, branchSlug }: { projectId: string; branchSlug?: string }) => {
|
9
|
-
const { data } = useGetTableOfContents({ projectId, branchSlug });
|
10
|
-
|
11
|
-
return data ? (
|
12
|
-
<TableOfContents
|
13
|
-
activeId="b3A6MTE0"
|
14
|
-
tableOfContents={data}
|
15
|
-
Link={({ children, ...props }) => {
|
16
|
-
return (
|
17
|
-
<a
|
18
|
-
onClick={() => {
|
19
|
-
console.log('Link clicked!', props);
|
20
|
-
}}
|
21
|
-
>
|
22
|
-
{children}
|
23
|
-
</a>
|
24
|
-
);
|
25
|
-
}}
|
26
|
-
style={{
|
27
|
-
width: '300px',
|
28
|
-
}}
|
29
|
-
/>
|
30
|
-
) : (
|
31
|
-
<>Loading</>
|
32
|
-
);
|
33
|
-
};
|
34
|
-
|
35
|
-
export default {
|
36
|
-
title: 'Public/TableOfContents',
|
37
|
-
component: TableOfContentsWrapper,
|
38
|
-
argTypes: {
|
39
|
-
projectId: { table: { category: 'Input' } },
|
40
|
-
branchSlug: { table: { category: 'Input' } },
|
41
|
-
platformUrl: { table: { category: 'Input' } },
|
42
|
-
},
|
43
|
-
args: {
|
44
|
-
projectId: 'cHJqOjYwNjYx',
|
45
|
-
branchSlug: '',
|
46
|
-
platformUrl: 'https://stoplight.io',
|
47
|
-
},
|
48
|
-
};
|
49
|
-
|
50
|
-
export const Playground: Story<{ nodeSlug: string; projectId: string; branchSlug?: string }> = args => (
|
51
|
-
<TableOfContentsWrapper {...args} />
|
52
|
-
);
|
53
|
-
|
54
|
-
Playground.storyName = 'Studio Demo';
|
@@ -1,51 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
CustomLinkComponent,
|
3
|
-
PoweredByLink,
|
4
|
-
TableOfContents as ElementsTableOfContents,
|
5
|
-
} from '@stoplight/elements-core';
|
6
|
-
import { BoxProps, Flex } from '@stoplight/mosaic';
|
7
|
-
import * as React from 'react';
|
8
|
-
|
9
|
-
import { ProjectTableOfContents } from '../../types';
|
10
|
-
|
11
|
-
export type TableOfContentsProps = BoxProps<'div'> & {
|
12
|
-
activeId: string;
|
13
|
-
tableOfContents: ProjectTableOfContents;
|
14
|
-
Link: CustomLinkComponent;
|
15
|
-
collapseTableOfContents?: boolean;
|
16
|
-
externalScrollbar?: boolean;
|
17
|
-
onLinkClick?(): void;
|
18
|
-
};
|
19
|
-
|
20
|
-
export const TableOfContents = ({
|
21
|
-
tableOfContents,
|
22
|
-
activeId,
|
23
|
-
Link,
|
24
|
-
collapseTableOfContents = false,
|
25
|
-
externalScrollbar,
|
26
|
-
onLinkClick,
|
27
|
-
...boxProps
|
28
|
-
}: TableOfContentsProps) => {
|
29
|
-
return (
|
30
|
-
<Flex bg="canvas-100" {...boxProps} flexDirection="col" maxH="full">
|
31
|
-
<Flex flexGrow flexShrink overflowY="auto">
|
32
|
-
<ElementsTableOfContents
|
33
|
-
tree={tableOfContents.items}
|
34
|
-
activeId={activeId}
|
35
|
-
Link={Link}
|
36
|
-
maxDepthOpenByDefault={collapseTableOfContents ? 0 : 1}
|
37
|
-
externalScrollbar={externalScrollbar}
|
38
|
-
onLinkClick={onLinkClick}
|
39
|
-
/>
|
40
|
-
</Flex>
|
41
|
-
|
42
|
-
{tableOfContents.hide_powered_by ? null : (
|
43
|
-
<PoweredByLink
|
44
|
-
source={activeId}
|
45
|
-
pathname={typeof window !== 'undefined' ? window.location.pathname : ''}
|
46
|
-
packageType="elements-dev-portal"
|
47
|
-
/>
|
48
|
-
)}
|
49
|
-
</Flex>
|
50
|
-
);
|
51
|
-
};
|
@@ -1,22 +0,0 @@
|
|
1
|
-
import { Box, Flex, Icon } from '@stoplight/mosaic';
|
2
|
-
import React from 'react';
|
3
|
-
|
4
|
-
export const UpgradeToStarter = () => (
|
5
|
-
<Flex
|
6
|
-
as="a"
|
7
|
-
href="https://stoplight.io/pricing/"
|
8
|
-
target="_blank"
|
9
|
-
rel="noreferrer noopener"
|
10
|
-
justify="center"
|
11
|
-
alignItems="center"
|
12
|
-
w="full"
|
13
|
-
minH="screen"
|
14
|
-
color="muted"
|
15
|
-
flexDirection="col"
|
16
|
-
>
|
17
|
-
<Icon icon={['fas', 'exclamation-triangle']} size="4x" />
|
18
|
-
<Box pt={3}>
|
19
|
-
Please upgrade your Stoplight Workspace to the Starter Plan to use Elements Dev Portal in production.
|
20
|
-
</Box>
|
21
|
-
</Flex>
|
22
|
-
);
|
package/src/consts.ts
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
const ROOT_CACHE_KEY = '@stoplight/elements-dev-portal/client-query';
|
2
|
-
|
3
|
-
export const devPortalCacheKeys = {
|
4
|
-
all: [ROOT_CACHE_KEY] as const,
|
5
|
-
|
6
|
-
projects: () => [ROOT_CACHE_KEY, 'projects'] as const,
|
7
|
-
project: (projectId: string) => [...devPortalCacheKeys.projects(), projectId] as const,
|
8
|
-
projectsList: () => [...devPortalCacheKeys.projects(), 'list'] as const,
|
9
|
-
projectDetails: (projectId: string) => [...devPortalCacheKeys.project(projectId), 'details'] as const,
|
10
|
-
|
11
|
-
branches: (projectId: string) => [...devPortalCacheKeys.project(projectId), 'branches'] as const,
|
12
|
-
branch: (projectId: string, branch: string) => [...devPortalCacheKeys.branches(projectId), branch] as const,
|
13
|
-
branchesList: (projectId: string) => [...devPortalCacheKeys.branches(projectId), 'list'] as const,
|
14
|
-
branchDetails: (projectId: string, branch: string) =>
|
15
|
-
[...devPortalCacheKeys.branch(projectId, branch), 'details'] as const,
|
16
|
-
branchTOC: (projectId: string, branch: string) => [...devPortalCacheKeys.branch(projectId, branch), 'toc'] as const,
|
17
|
-
|
18
|
-
branchNodes: (projectId: string, branch: string) =>
|
19
|
-
[...devPortalCacheKeys.branch(projectId, branch), 'nodes'] as const,
|
20
|
-
branchNode: (projectId: string, branch: string, node: string) =>
|
21
|
-
[...devPortalCacheKeys.branchNodes(projectId, branch), node] as const,
|
22
|
-
branchNodesList: (projectId: string, branch: string) =>
|
23
|
-
[...devPortalCacheKeys.branchNodes(projectId, branch), 'list'] as const,
|
24
|
-
branchNodeDetails: (projectId: string, branch: string, node: string) =>
|
25
|
-
[...devPortalCacheKeys.branchNode(projectId, branch, node), 'details'] as const,
|
26
|
-
|
27
|
-
search: () => [...devPortalCacheKeys.all, 'search'],
|
28
|
-
searchNodes: (filters: { projectIds?: string[]; branchSlug?: string; workspaceId?: string; search?: string }) => [
|
29
|
-
...devPortalCacheKeys.search(),
|
30
|
-
filters,
|
31
|
-
],
|
32
|
-
};
|
@@ -1,78 +0,0 @@
|
|
1
|
-
import '@testing-library/jest-dom';
|
2
|
-
|
3
|
-
import { render, screen } from '@testing-library/react';
|
4
|
-
import fetchMock from 'jest-fetch-mock';
|
5
|
-
import * as React from 'react';
|
6
|
-
|
7
|
-
import branches from '../__fixtures__/branches.json';
|
8
|
-
import nodeContent from '../__fixtures__/node-content.json';
|
9
|
-
import tableOfContents from '../__fixtures__/table-of-contents.json';
|
10
|
-
import { StoplightProject } from './StoplightProject';
|
11
|
-
|
12
|
-
describe('Stoplight Project', () => {
|
13
|
-
beforeEach(() => {
|
14
|
-
fetchMock.mockResponse(request => {
|
15
|
-
if (request.url.match(/\/api\/v1\/projects\/[a-zA-Z0-9_.-]+\/table-of-contents/i)) {
|
16
|
-
return Promise.resolve({
|
17
|
-
body: JSON.stringify(tableOfContents),
|
18
|
-
status: 200,
|
19
|
-
statusText: 'OK',
|
20
|
-
headers: [],
|
21
|
-
});
|
22
|
-
} else if (request.url.match(/\/api\/v1\/projects\/[a-zA-Z0-9_.-]+\/nodes/i)) {
|
23
|
-
return Promise.resolve({
|
24
|
-
body: JSON.stringify(nodeContent),
|
25
|
-
status: 200,
|
26
|
-
statusText: 'OK',
|
27
|
-
headers: [],
|
28
|
-
});
|
29
|
-
} else if (request.url.match(/\/api\/v1\/projects\/[a-zA-Z0-9_.-]+\/branches/i)) {
|
30
|
-
return Promise.resolve({
|
31
|
-
body: JSON.stringify(branches),
|
32
|
-
status: 200,
|
33
|
-
statusText: 'OK',
|
34
|
-
headers: [],
|
35
|
-
});
|
36
|
-
} else {
|
37
|
-
return Promise.resolve({ status: 404, statusText: 'Not Found', headers: [], body: '' });
|
38
|
-
}
|
39
|
-
});
|
40
|
-
fetchMock.enableMocks();
|
41
|
-
});
|
42
|
-
|
43
|
-
afterEach(() => {
|
44
|
-
fetchMock.disableMocks();
|
45
|
-
});
|
46
|
-
|
47
|
-
it('loads correctly using memory router', async () => {
|
48
|
-
render(<StoplightProject router="memory" projectId="cHJqOjYwNjYx" platformUrl="https://stoplight.io" />);
|
49
|
-
|
50
|
-
expect(
|
51
|
-
await screen.findByText(
|
52
|
-
'Markdown is supported in descriptions. Add information here for users to get accustomed to endpoints',
|
53
|
-
{},
|
54
|
-
{ timeout: 10000 },
|
55
|
-
),
|
56
|
-
).toBeInTheDocument();
|
57
|
-
});
|
58
|
-
|
59
|
-
it('loads correctly using static router', async () => {
|
60
|
-
render(
|
61
|
-
<StoplightProject
|
62
|
-
router="static"
|
63
|
-
projectId="cHJqOjYwNjYx"
|
64
|
-
basePath=""
|
65
|
-
staticRouterPath="/b3A6Mzg5NDM2-create-todo"
|
66
|
-
platformUrl="https://stoplight.io"
|
67
|
-
/>,
|
68
|
-
);
|
69
|
-
|
70
|
-
expect(
|
71
|
-
await screen.findByText(
|
72
|
-
'Markdown is supported in descriptions. Add information here for users to get accustomed to endpoints',
|
73
|
-
{},
|
74
|
-
{ timeout: 10000 },
|
75
|
-
),
|
76
|
-
).toBeInTheDocument();
|
77
|
-
});
|
78
|
-
});
|
@@ -1,28 +0,0 @@
|
|
1
|
-
import { Story } from '@storybook/react';
|
2
|
-
import * as React from 'react';
|
3
|
-
|
4
|
-
import { StoplightProject, StoplightProjectProps } from './StoplightProject';
|
5
|
-
|
6
|
-
export default {
|
7
|
-
title: 'Public/StoplightProject',
|
8
|
-
component: StoplightProject,
|
9
|
-
argTypes: {
|
10
|
-
projectId: { table: { category: 'Input' } },
|
11
|
-
hideTryIt: { table: { category: 'Input' } },
|
12
|
-
hideMocking: { table: { category: 'Input' } },
|
13
|
-
basePath: { table: { category: 'Routing' } },
|
14
|
-
router: { table: { category: 'Routing' } },
|
15
|
-
platformUrl: { table: { category: 'Advanced' } },
|
16
|
-
},
|
17
|
-
args: {
|
18
|
-
router: 'memory',
|
19
|
-
platformUrl: 'https://stoplight.io',
|
20
|
-
},
|
21
|
-
};
|
22
|
-
|
23
|
-
export const Playground: Story<StoplightProjectProps> = args => <StoplightProject {...args} />;
|
24
|
-
Playground.storyName = 'Studio Demo';
|
25
|
-
Playground.args = {
|
26
|
-
projectId: 'cHJqOjYwNjYx',
|
27
|
-
platformUrl: 'https://stoplight.io',
|
28
|
-
};
|
@@ -1,213 +0,0 @@
|
|
1
|
-
import {
|
2
|
-
findFirstNode,
|
3
|
-
ReactRouterMarkdownLink,
|
4
|
-
RouterTypeContext,
|
5
|
-
RoutingProps,
|
6
|
-
SidebarLayout,
|
7
|
-
useRouter,
|
8
|
-
withStyles,
|
9
|
-
} from '@stoplight/elements-core';
|
10
|
-
import * as React from 'react';
|
11
|
-
import { Link, Redirect, Route, useHistory, useParams } from 'react-router-dom';
|
12
|
-
|
13
|
-
import { BranchSelector } from '../components/BranchSelector';
|
14
|
-
import { DevPortalProvider } from '../components/DevPortalProvider';
|
15
|
-
import { Forbidden } from '../components/Forbidden';
|
16
|
-
import { Loading } from '../components/Loading';
|
17
|
-
import { NodeContent } from '../components/NodeContent';
|
18
|
-
import { NotFound } from '../components/NotFound';
|
19
|
-
import { TableOfContents } from '../components/TableOfContents';
|
20
|
-
import { UpgradeToStarter } from '../components/UpgradeToStarter';
|
21
|
-
import { ResponseError } from '../handlers/getNodeContent';
|
22
|
-
import { useGetBranches } from '../hooks/useGetBranches';
|
23
|
-
import { useGetNodeContent } from '../hooks/useGetNodeContent';
|
24
|
-
import { useGetTableOfContents } from '../hooks/useGetTableOfContents';
|
25
|
-
|
26
|
-
export interface StoplightProjectProps extends RoutingProps {
|
27
|
-
/**
|
28
|
-
* The ID of the Stoplight Project.
|
29
|
-
* @example "d2s6NDE1NTU"
|
30
|
-
*/
|
31
|
-
projectId: string;
|
32
|
-
/**
|
33
|
-
* If your company runs an on-premise deployment of Stoplight,
|
34
|
-
* set this prop to point the StoplightProject component at the URL of that instance.
|
35
|
-
*/
|
36
|
-
platformUrl?: string;
|
37
|
-
|
38
|
-
/**
|
39
|
-
* Allows to hide TryIt component
|
40
|
-
*/
|
41
|
-
hideTryIt?: boolean;
|
42
|
-
|
43
|
-
/**
|
44
|
-
* Allows to hide mocking button
|
45
|
-
*/
|
46
|
-
hideMocking?: boolean;
|
47
|
-
|
48
|
-
/**
|
49
|
-
* Allows to hide export button
|
50
|
-
* @default false
|
51
|
-
*/
|
52
|
-
hideExport?: boolean;
|
53
|
-
|
54
|
-
/**
|
55
|
-
* If set to true, all table of contents panels will be collapsed.
|
56
|
-
* @default false
|
57
|
-
*/
|
58
|
-
collapseTableOfContents?: boolean;
|
59
|
-
|
60
|
-
/**
|
61
|
-
* Fetch credentials policy for TryIt component
|
62
|
-
* For more information: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
|
63
|
-
* @default "omit"
|
64
|
-
*/
|
65
|
-
|
66
|
-
tryItCredentialsPolicy?: 'omit' | 'include' | 'same-origin';
|
67
|
-
|
68
|
-
/**
|
69
|
-
* URL of a CORS proxy that will be used to send requests in TryIt.
|
70
|
-
* Provided url will be prepended to an URL of an actual request.
|
71
|
-
* @default false
|
72
|
-
*/
|
73
|
-
tryItCorsProxy?: string;
|
74
|
-
}
|
75
|
-
|
76
|
-
const StoplightProjectImpl: React.FC<StoplightProjectProps> = ({
|
77
|
-
projectId,
|
78
|
-
hideTryIt,
|
79
|
-
hideMocking,
|
80
|
-
hideExport,
|
81
|
-
collapseTableOfContents = false,
|
82
|
-
tryItCredentialsPolicy,
|
83
|
-
tryItCorsProxy,
|
84
|
-
}) => {
|
85
|
-
const { branchSlug = '', nodeSlug = '' } = useParams<{ branchSlug?: string; nodeSlug: string }>();
|
86
|
-
const history = useHistory();
|
87
|
-
|
88
|
-
const { data: tableOfContents, isFetched: isTocFetched } = useGetTableOfContents({ projectId, branchSlug });
|
89
|
-
const { data: branches } = useGetBranches({ projectId });
|
90
|
-
const {
|
91
|
-
data: node,
|
92
|
-
isLoading: isLoadingNode,
|
93
|
-
isError,
|
94
|
-
error: nodeError,
|
95
|
-
} = useGetNodeContent({
|
96
|
-
nodeSlug,
|
97
|
-
projectId,
|
98
|
-
branchSlug,
|
99
|
-
});
|
100
|
-
const container = React.useRef<HTMLDivElement>(null);
|
101
|
-
|
102
|
-
if (!nodeSlug && isTocFetched && tableOfContents?.items) {
|
103
|
-
const firstNode = findFirstNode(tableOfContents.items);
|
104
|
-
if (firstNode) {
|
105
|
-
return <Redirect to={branchSlug ? `/branches/${branchSlug}/${firstNode.slug}` : `/${firstNode.slug}`} />;
|
106
|
-
}
|
107
|
-
}
|
108
|
-
|
109
|
-
let elem: JSX.Element;
|
110
|
-
if (isLoadingNode || !isTocFetched) {
|
111
|
-
elem = <Loading />;
|
112
|
-
} else if (isError) {
|
113
|
-
if (nodeError instanceof ResponseError) {
|
114
|
-
if (nodeError.code === 402) {
|
115
|
-
elem = <UpgradeToStarter />;
|
116
|
-
} else if (nodeError.code === 403) {
|
117
|
-
elem = <Forbidden />;
|
118
|
-
} else {
|
119
|
-
elem = <NotFound />;
|
120
|
-
}
|
121
|
-
} else {
|
122
|
-
elem = <NotFound />;
|
123
|
-
}
|
124
|
-
} else if (!node) {
|
125
|
-
elem = <NotFound />;
|
126
|
-
} else if (node?.slug && nodeSlug !== node.slug) {
|
127
|
-
// Handle redirect to node's slug
|
128
|
-
return <Redirect to={branchSlug ? `/branches/${branchSlug}/${node.slug}` : `/${node.slug}`} />;
|
129
|
-
} else {
|
130
|
-
elem = (
|
131
|
-
<NodeContent
|
132
|
-
node={node}
|
133
|
-
Link={ReactRouterMarkdownLink}
|
134
|
-
hideTryIt={hideTryIt}
|
135
|
-
hideMocking={hideMocking}
|
136
|
-
hideExport={hideExport}
|
137
|
-
tryItCredentialsPolicy={tryItCredentialsPolicy}
|
138
|
-
tryItCorsProxy={tryItCorsProxy}
|
139
|
-
/>
|
140
|
-
);
|
141
|
-
}
|
142
|
-
|
143
|
-
const handleTocClick = () => {
|
144
|
-
if (container.current) {
|
145
|
-
container.current.scrollIntoView();
|
146
|
-
}
|
147
|
-
};
|
148
|
-
|
149
|
-
return (
|
150
|
-
<SidebarLayout
|
151
|
-
ref={container}
|
152
|
-
sidebar={
|
153
|
-
<>
|
154
|
-
{branches && branches.length > 1 ? (
|
155
|
-
<BranchSelector
|
156
|
-
branchSlug={branchSlug}
|
157
|
-
branches={branches}
|
158
|
-
onChange={branch =>
|
159
|
-
history.push(branch.is_default ? `/${nodeSlug}` : `/branches/${branch.slug}/${nodeSlug}`)
|
160
|
-
}
|
161
|
-
/>
|
162
|
-
) : null}
|
163
|
-
{tableOfContents ? (
|
164
|
-
<TableOfContents
|
165
|
-
activeId={node?.id || nodeSlug?.split('-')[0] || ''}
|
166
|
-
tableOfContents={tableOfContents}
|
167
|
-
Link={Link}
|
168
|
-
collapseTableOfContents={collapseTableOfContents}
|
169
|
-
onLinkClick={handleTocClick}
|
170
|
-
/>
|
171
|
-
) : null}
|
172
|
-
</>
|
173
|
-
}
|
174
|
-
>
|
175
|
-
{elem}
|
176
|
-
</SidebarLayout>
|
177
|
-
);
|
178
|
-
};
|
179
|
-
|
180
|
-
const StoplightProjectRouter = ({
|
181
|
-
platformUrl,
|
182
|
-
basePath = '/',
|
183
|
-
staticRouterPath = '',
|
184
|
-
router = 'history',
|
185
|
-
...props
|
186
|
-
}: StoplightProjectProps) => {
|
187
|
-
const { Router, routerProps } = useRouter(router, basePath, staticRouterPath);
|
188
|
-
|
189
|
-
return (
|
190
|
-
<DevPortalProvider platformUrl={platformUrl}>
|
191
|
-
<RouterTypeContext.Provider value={router}>
|
192
|
-
<Router {...routerProps} key={basePath}>
|
193
|
-
<Route path="/branches/:branchSlug/:nodeSlug" exact>
|
194
|
-
<StoplightProjectImpl {...props} />
|
195
|
-
</Route>
|
196
|
-
|
197
|
-
<Route path="/:nodeSlug" exact>
|
198
|
-
<StoplightProjectImpl {...props} />
|
199
|
-
</Route>
|
200
|
-
|
201
|
-
<Route path="/" exact>
|
202
|
-
<StoplightProjectImpl {...props} />
|
203
|
-
</Route>
|
204
|
-
</Router>
|
205
|
-
</RouterTypeContext.Provider>
|
206
|
-
</DevPortalProvider>
|
207
|
-
);
|
208
|
-
};
|
209
|
-
|
210
|
-
/**
|
211
|
-
* The StoplightProject component displays a traditional documentation UI for an existing Stoplight Project.
|
212
|
-
*/
|
213
|
-
export const StoplightProject = withStyles(StoplightProjectRouter);
|
@@ -1,30 +0,0 @@
|
|
1
|
-
import fetchMock from 'jest-fetch-mock';
|
2
|
-
|
3
|
-
import { getBranches } from '../getBranches';
|
4
|
-
|
5
|
-
describe('getBranches', () => {
|
6
|
-
beforeEach(() => {
|
7
|
-
fetchMock.resetMocks();
|
8
|
-
localStorage.clear();
|
9
|
-
});
|
10
|
-
|
11
|
-
it('should URI encode the parameters in the request URL', async () => {
|
12
|
-
fetchMock.mockResolvedValue(
|
13
|
-
new Response('{}', {
|
14
|
-
status: 200,
|
15
|
-
statusText: 'OK',
|
16
|
-
headers: [],
|
17
|
-
}),
|
18
|
-
);
|
19
|
-
|
20
|
-
await getBranches({
|
21
|
-
projectId: 'some/slash',
|
22
|
-
});
|
23
|
-
|
24
|
-
expect(fetchMock).toBeCalledWith('https://stoplight.io/api/v1/projects/some%2Fslash/branches', {
|
25
|
-
headers: expect.objectContaining({
|
26
|
-
'Stoplight-Elements-Version': expect.any(String),
|
27
|
-
}),
|
28
|
-
});
|
29
|
-
});
|
30
|
-
});
|
@@ -1,35 +0,0 @@
|
|
1
|
-
import fetchMock from 'jest-fetch-mock';
|
2
|
-
|
3
|
-
import { getNodeContent } from '../getNodeContent';
|
4
|
-
|
5
|
-
describe('getNodeContent', () => {
|
6
|
-
beforeEach(() => {
|
7
|
-
fetchMock.resetMocks();
|
8
|
-
localStorage.clear();
|
9
|
-
});
|
10
|
-
|
11
|
-
it('should URI encode the parameters in the request URL', async () => {
|
12
|
-
fetchMock.mockResolvedValue(
|
13
|
-
new Response('{}', {
|
14
|
-
status: 200,
|
15
|
-
statusText: 'OK',
|
16
|
-
headers: [],
|
17
|
-
}),
|
18
|
-
);
|
19
|
-
|
20
|
-
await getNodeContent({
|
21
|
-
nodeSlug: 'node&slug',
|
22
|
-
projectId: 'some/slash',
|
23
|
-
branchSlug: 'test+branch',
|
24
|
-
});
|
25
|
-
|
26
|
-
expect(fetchMock).toBeCalledWith(
|
27
|
-
'https://stoplight.io/api/v1/projects/some%2Fslash/nodes/node%26slug?branch=test%2Bbranch',
|
28
|
-
{
|
29
|
-
headers: expect.objectContaining({
|
30
|
-
'Stoplight-Elements-Version': expect.any(String),
|
31
|
-
}),
|
32
|
-
},
|
33
|
-
);
|
34
|
-
});
|
35
|
-
});
|