@jpmorganchase/elemental-dev-portal 1.0.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
});
|