@stoplight/elements 7.16.6 → 8.0.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/index.js CHANGED
@@ -13,6 +13,7 @@ var reactQuery = require('react-query');
13
13
  var reactRouterDom = require('react-router-dom');
14
14
  var yaml = require('@stoplight/yaml');
15
15
  var saver = require('file-saver');
16
+ var oas = require('@stoplight/http-spec/oas');
16
17
  var oas2 = require('@stoplight/http-spec/oas2');
17
18
  var oas3 = require('@stoplight/http-spec/oas3');
18
19
  var json = require('@stoplight/json');
@@ -49,13 +50,12 @@ var get__default = /*#__PURE__*/_interopDefaultLegacy(get);
49
50
  var isObject__default = /*#__PURE__*/_interopDefaultLegacy(isObject);
50
51
  var last__default = /*#__PURE__*/_interopDefaultLegacy(last);
51
52
 
52
- const computeTagGroups = (serviceNode) => {
53
+ function computeTagGroups(serviceNode, nodeType) {
53
54
  const groupsByTagId = {};
54
55
  const ungrouped = [];
55
56
  const lowerCaseServiceTags = serviceNode.tags.map(tn => tn.toLowerCase());
56
- for (const node of serviceNode.children) {
57
- if (node.type !== types.NodeType.HttpOperation)
58
- continue;
57
+ const groupableNodes = serviceNode.children.filter(n => n.type === nodeType);
58
+ for (const node of groupableNodes) {
59
59
  const tagName = node.tags[0];
60
60
  if (tagName) {
61
61
  const tagId = tagName.toLowerCase();
@@ -91,7 +91,7 @@ const computeTagGroups = (serviceNode) => {
91
91
  })
92
92
  .map(([, tagGroup]) => tagGroup);
93
93
  return { groups: orderedTagGroups, ungrouped };
94
- };
94
+ }
95
95
  const defaultComputerAPITreeConfig = {
96
96
  hideSchemas: false,
97
97
  hideInternal: false,
@@ -106,12 +106,12 @@ const computeAPITree = (serviceNode, config = {}) => {
106
106
  type: 'overview',
107
107
  meta: '',
108
108
  });
109
- const operationNodes = serviceNode.children.filter(node => node.type === types.NodeType.HttpOperation);
110
- if (operationNodes.length) {
109
+ const hasOperationNodes = serviceNode.children.some(node => node.type === types.NodeType.HttpOperation);
110
+ if (hasOperationNodes) {
111
111
  tree.push({
112
112
  title: 'Endpoints',
113
113
  });
114
- const { groups, ungrouped } = computeTagGroups(serviceNode);
114
+ const { groups, ungrouped } = computeTagGroups(serviceNode, types.NodeType.HttpOperation);
115
115
  ungrouped.forEach(operationNode => {
116
116
  if (mergedConfig.hideInternal && operationNode.data.internal) {
117
117
  return;
@@ -141,13 +141,54 @@ const computeAPITree = (serviceNode, config = {}) => {
141
141
  tree.push({
142
142
  title: group.title,
143
143
  items,
144
+ itemsType: 'http_operation',
145
+ });
146
+ }
147
+ });
148
+ }
149
+ const hasWebhookNodes = serviceNode.children.some(node => node.type === types.NodeType.HttpWebhook);
150
+ if (hasWebhookNodes) {
151
+ tree.push({
152
+ title: 'Webhooks',
153
+ });
154
+ const { groups, ungrouped } = computeTagGroups(serviceNode, types.NodeType.HttpWebhook);
155
+ ungrouped.forEach(operationNode => {
156
+ if (mergedConfig.hideInternal && operationNode.data.internal) {
157
+ return;
158
+ }
159
+ tree.push({
160
+ id: operationNode.uri,
161
+ slug: operationNode.uri,
162
+ title: operationNode.name,
163
+ type: operationNode.type,
164
+ meta: operationNode.data.method,
165
+ });
166
+ });
167
+ groups.forEach(group => {
168
+ const items = group.items.flatMap(operationNode => {
169
+ if (mergedConfig.hideInternal && operationNode.data.internal) {
170
+ return [];
171
+ }
172
+ return {
173
+ id: operationNode.uri,
174
+ slug: operationNode.uri,
175
+ title: operationNode.name,
176
+ type: operationNode.type,
177
+ meta: operationNode.data.method,
178
+ };
179
+ });
180
+ if (items.length > 0) {
181
+ tree.push({
182
+ title: group.title,
183
+ items,
184
+ itemsType: 'http_webhook',
144
185
  });
145
186
  }
146
187
  });
147
188
  }
148
189
  let schemaNodes = serviceNode.children.filter(node => node.type === types.NodeType.Model);
149
190
  if (mergedConfig.hideInternal) {
150
- schemaNodes = schemaNodes.filter(node => !node.data['x-internal']);
191
+ schemaNodes = schemaNodes.filter(n => !isInternal(n));
151
192
  }
152
193
  if (!mergedConfig.hideSchemas && schemaNodes.length) {
153
194
  tree.push({
@@ -181,7 +222,7 @@ const findFirstNodeSlug = (tree) => {
181
222
  };
182
223
  const isInternal = (node) => {
183
224
  const data = node.data;
184
- if (elementsCore.isHttpOperation(data)) {
225
+ if (elementsCore.isHttpOperation(data) || elementsCore.isHttpWebhookOperation(data)) {
185
226
  return !!data.internal;
186
227
  }
187
228
  if (elementsCore.isHttpService(data)) {
@@ -191,7 +232,12 @@ const isInternal = (node) => {
191
232
  };
192
233
 
193
234
  const itemMatchesHash = (hash, item) => {
194
- return hash.substr(1) === `${item.data.path}-${item.data.method}`;
235
+ if (item.type === types.NodeType.HttpOperation) {
236
+ return hash.substr(1) === `${item.data.path}-${item.data.method}`;
237
+ }
238
+ else {
239
+ return hash.substr(1) === `${item.data.name}-${item.data.method}`;
240
+ }
195
241
  };
196
242
  const TryItContext = React__namespace.createContext({
197
243
  hideTryIt: false,
@@ -209,14 +255,19 @@ const LocationContext = React__namespace.createContext({
209
255
  });
210
256
  LocationContext.displayName = 'LocationContext';
211
257
  const APIWithStackedLayout = ({ serviceNode, hideTryIt, hideExport, exportProps, tryItCredentialsPolicy, tryItCorsProxy, showPoweredByLink = true, location, }) => {
212
- const { groups } = computeTagGroups(serviceNode);
258
+ const { groups: operationGroups } = computeTagGroups(serviceNode, types.NodeType.HttpOperation);
259
+ const { groups: webhookGroups } = computeTagGroups(serviceNode, types.NodeType.HttpWebhook);
213
260
  return (React__namespace.createElement(LocationContext.Provider, { value: { location } },
214
261
  React__namespace.createElement(TryItContext.Provider, { value: { hideTryIt, tryItCredentialsPolicy, corsProxy: tryItCorsProxy } },
215
262
  React__namespace.createElement(mosaic.Flex, { w: "full", flexDirection: "col", m: "auto", className: "sl-max-w-4xl" },
216
263
  React__namespace.createElement(mosaic.Box, { w: "full", borderB: true },
217
264
  React__namespace.createElement(elementsCore.Docs, { className: "sl-mx-auto", nodeData: serviceNode.data, nodeTitle: serviceNode.name, nodeType: types.NodeType.HttpService, location: location, layoutOptions: { showPoweredByLink, hideExport }, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy })),
218
- groups.map(group => (React__namespace.createElement(Group, { key: group.title, group: group })))))));
265
+ operationGroups.length > 0 && webhookGroups.length > 0 ? React__namespace.createElement(mosaic.Heading, { size: 2 }, "Endpoints") : null,
266
+ operationGroups.map(group => (React__namespace.createElement(Group, { key: group.title, group: group }))),
267
+ webhookGroups.length > 0 ? React__namespace.createElement(mosaic.Heading, { size: 2 }, "Webhooks") : null,
268
+ webhookGroups.map(group => (React__namespace.createElement(Group, { key: group.title, group: group })))))));
219
269
  };
270
+ APIWithStackedLayout.displayName = 'APIWithStackedLayout';
220
271
  const Group = React__namespace.memo(({ group }) => {
221
272
  const [isExpanded, setIsExpanded] = React__namespace.useState(false);
222
273
  const scrollRef = React__namespace.useRef(null);
@@ -243,6 +294,7 @@ const Group = React__namespace.memo(({ group }) => {
243
294
  return React__namespace.createElement(Item, { key: item.uri, item: item });
244
295
  }))));
245
296
  });
297
+ Group.displayName = 'Group';
246
298
  const Item = React__namespace.memo(({ item }) => {
247
299
  const { location } = React__namespace.useContext(LocationContext);
248
300
  const { hash } = location;
@@ -264,7 +316,7 @@ const Item = React__namespace.memo(({ item }) => {
264
316
  return (React__namespace.createElement(mosaic.Box, { ref: scrollRef, w: "full", my: 2, border: true, borderColor: { default: isExpanded ? 'light' : 'transparent', hover: 'light' }, bg: { default: isExpanded ? 'code' : 'transparent', hover: 'code' } },
265
317
  React__namespace.createElement(mosaic.Flex, { mx: "auto", alignItems: "center", cursor: "pointer", fontSize: "lg", p: 2, onClick: onClick, color: "current" },
266
318
  React__namespace.createElement(mosaic.Box, { w: 24, textTransform: "uppercase", textAlign: "center", fontWeight: "semibold", border: true, rounded: true, px: 2, bg: "canvas", className: cn__default["default"](`sl-mr-5 sl-text-base`, `sl-text-${color}`, `sl-border-${color}`) }, item.data.method || 'UNKNOWN'),
267
- React__namespace.createElement(mosaic.Box, { flex: 1, fontWeight: "medium", wordBreak: "all" }, item.data.path),
319
+ React__namespace.createElement(mosaic.Box, { flex: 1, fontWeight: "medium", wordBreak: "all" }, item.type === types.NodeType.HttpOperation ? item.data.path : item.name),
268
320
  isDeprecated && React__namespace.createElement(elementsCore.DeprecatedBadge, null)),
269
321
  React__namespace.createElement(Collapse, { isOpen: isExpanded },
270
322
  React__namespace.createElement(mosaic.Box, { flex: 1, p: 2, fontWeight: "medium", mx: "auto", fontSize: "xl" }, item.name),
@@ -278,10 +330,37 @@ const Item = React__namespace.memo(({ item }) => {
278
330
  React__namespace.createElement(mosaic.TabPanel, null,
279
331
  React__namespace.createElement(elementsCore.TryItWithRequestSamples, { httpOperation: item.data, tryItCredentialsPolicy: tryItCredentialsPolicy, corsProxy: corsProxy }))))))));
280
332
  });
333
+ Item.displayName = 'Item';
281
334
  const Collapse = ({ isOpen, children }) => {
282
335
  if (!isOpen)
283
336
  return null;
284
337
  return React__namespace.createElement(mosaic.Box, null, children);
338
+ };
339
+ Collapse.displayName = 'Collapse';
340
+
341
+ const APIWithResponsiveSidebarLayout = ({ serviceNode, logo, hideTryIt, compact, hideSchemas, hideInternal, hideExport, exportProps, tryItCredentialsPolicy, tryItCorsProxy, }) => {
342
+ const container = React__namespace.useRef(null);
343
+ const tree = React__namespace.useMemo(() => computeAPITree(serviceNode, { hideSchemas, hideInternal }), [serviceNode, hideSchemas, hideInternal]);
344
+ const location = reactRouterDom.useLocation();
345
+ const { pathname } = location;
346
+ const isRootPath = !pathname || pathname === '/';
347
+ const node = isRootPath ? serviceNode : serviceNode.children.find(child => child.uri === pathname);
348
+ const layoutOptions = React__namespace.useMemo(() => ({ hideTryIt: hideTryIt, compact: compact, hideExport: hideExport || (node === null || node === void 0 ? void 0 : node.type) !== types.NodeType.HttpService }), [hideTryIt, hideExport, node, compact]);
349
+ if (!node) {
350
+ const firstSlug = findFirstNodeSlug(tree);
351
+ if (firstSlug) {
352
+ return React__namespace.createElement(reactRouterDom.Redirect, { to: firstSlug });
353
+ }
354
+ }
355
+ if (hideInternal && node && isInternal(node)) {
356
+ return React__namespace.createElement(reactRouterDom.Redirect, { to: "/" });
357
+ }
358
+ const handleTocClick = () => {
359
+ if (container.current) {
360
+ container.current.scrollIntoView();
361
+ }
362
+ };
363
+ return (React__namespace.createElement(elementsCore.ResponsiveSidebarLayout, { onTocClick: handleTocClick, tree: tree, logo: logo !== null && logo !== void 0 ? logo : serviceNode.data.logo, ref: container, name: serviceNode.name }, node && (React__namespace.createElement(elementsCore.ParsedDocs, { key: pathname, uri: pathname, node: node, nodeTitle: node.name, layoutOptions: layoutOptions, location: location, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy }))));
285
364
  };
286
365
 
287
366
  const APIWithSidebarLayout = ({ serviceNode, logo, hideTryIt, hideSchemas, hideInternal, hideExport, exportProps, tryItCredentialsPolicy, tryItCorsProxy, }) => {
@@ -301,26 +380,32 @@ const APIWithSidebarLayout = ({ serviceNode, logo, hideTryIt, hideSchemas, hideI
301
380
  if (hideInternal && node && isInternal(node)) {
302
381
  return React__namespace.createElement(reactRouterDom.Redirect, { to: "/" });
303
382
  }
383
+ const sidebar = (React__namespace.createElement(Sidebar, { serviceNode: serviceNode, logo: logo, container: container, pathname: pathname, tree: tree }));
384
+ return (React__namespace.createElement(elementsCore.SidebarLayout, { ref: container, sidebar: sidebar }, node && (React__namespace.createElement(elementsCore.ParsedDocs, { key: pathname, uri: pathname, node: node, nodeTitle: node.name, layoutOptions: layoutOptions, location: location, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy }))));
385
+ };
386
+ const Sidebar = ({ serviceNode, logo, container, pathname, tree }) => {
304
387
  const handleTocClick = () => {
305
388
  if (container.current) {
306
389
  container.current.scrollIntoView();
307
390
  }
308
391
  };
309
- const sidebar = (React__namespace.createElement(React__namespace.Fragment, null,
392
+ return (React__namespace.createElement(React__namespace.Fragment, null,
310
393
  React__namespace.createElement(mosaic.Flex, { ml: 4, mb: 5, alignItems: "center" },
311
394
  logo ? (React__namespace.createElement(elementsCore.Logo, { logo: { url: logo, altText: 'logo' } })) : (serviceNode.data.logo && React__namespace.createElement(elementsCore.Logo, { logo: serviceNode.data.logo })),
312
395
  React__namespace.createElement(mosaic.Heading, { size: 4 }, serviceNode.name)),
313
396
  React__namespace.createElement(mosaic.Flex, { flexGrow: true, flexShrink: true, overflowY: "auto", direction: "col" },
314
397
  React__namespace.createElement(elementsCore.TableOfContents, { tree: tree, activeId: pathname, Link: reactRouterDom.Link, onLinkClick: handleTocClick })),
315
398
  React__namespace.createElement(elementsCore.PoweredByLink, { source: serviceNode.name, pathname: pathname, packageType: "elements" })));
316
- return (React__namespace.createElement(elementsCore.SidebarLayout, { ref: container, sidebar: sidebar }, node && (React__namespace.createElement(elementsCore.ParsedDocs, { key: pathname, uri: pathname, node: node, nodeTitle: node.name, layoutOptions: layoutOptions, location: location, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy }))));
317
- };
399
+ };
400
+ Sidebar.displayName = 'Sidebar';
318
401
 
319
402
  var NodeTypes;
320
403
  (function (NodeTypes) {
321
404
  NodeTypes["Paths"] = "paths";
322
405
  NodeTypes["Path"] = "path";
323
406
  NodeTypes["Operation"] = "operation";
407
+ NodeTypes["Webhooks"] = "webhooks";
408
+ NodeTypes["Webhook"] = "webhook";
324
409
  NodeTypes["Components"] = "components";
325
410
  NodeTypes["Models"] = "models";
326
411
  NodeTypes["Model"] = "model";
@@ -372,6 +457,22 @@ const oas3SourceMap = [
372
457
  },
373
458
  ],
374
459
  },
460
+ {
461
+ match: 'webhooks',
462
+ type: NodeTypes.Webhooks,
463
+ children: [
464
+ {
465
+ notMatch: '^x-',
466
+ type: NodeTypes.Webhook,
467
+ children: [
468
+ {
469
+ match: 'get|post|put|delete|options|head|patch|trace',
470
+ type: NodeTypes.Webhook,
471
+ },
472
+ ],
473
+ },
474
+ ],
475
+ },
375
476
  {
376
477
  match: 'components',
377
478
  type: NodeTypes.Components,
@@ -426,7 +527,7 @@ function computeServiceNode(document, map, transformService, transformOperation)
426
527
  return serviceNode;
427
528
  }
428
529
  function computeChildNodes(document, data, map, transformer, parentUri = '') {
429
- var _a;
530
+ var _a, _b;
430
531
  const nodes = [];
431
532
  if (!isObject__default["default"](data))
432
533
  return nodes;
@@ -439,7 +540,12 @@ function computeChildNodes(document, data, map, transformer, parentUri = '') {
439
540
  if (match.type === NodeTypes.Operation && jsonPath.length === 3) {
440
541
  const path = String(jsonPath[1]);
441
542
  const method = String(jsonPath[2]);
442
- const operationDocument = transformer({ document, path, method });
543
+ const operationDocument = transformer({
544
+ document,
545
+ name: path,
546
+ method,
547
+ config: oas.OPERATION_CONFIG,
548
+ });
443
549
  let parsedUri;
444
550
  const encodedPath = String(json.encodePointerFragment(path));
445
551
  if (operationDocument.iid) {
@@ -456,6 +562,31 @@ function computeChildNodes(document, data, map, transformer, parentUri = '') {
456
562
  tags: ((_a = operationDocument.tags) === null || _a === void 0 ? void 0 : _a.map(tag => tag.name)) || [],
457
563
  });
458
564
  }
565
+ else if (match.type === NodeTypes.Webhook && jsonPath.length === 3) {
566
+ const name = String(jsonPath[1]);
567
+ const method = String(jsonPath[2]);
568
+ const webhookDocument = transformer({
569
+ document,
570
+ name,
571
+ method,
572
+ config: oas.WEBHOOK_CONFIG,
573
+ });
574
+ let parsedUri;
575
+ const encodedPath = String(json.encodePointerFragment(name));
576
+ if (webhookDocument.iid) {
577
+ parsedUri = `/webhooks/${webhookDocument.iid}`;
578
+ }
579
+ else {
580
+ parsedUri = uri.replace(encodedPath, elementsCore.slugify(name));
581
+ }
582
+ nodes.push({
583
+ type: types.NodeType.HttpWebhook,
584
+ uri: parsedUri,
585
+ data: webhookDocument,
586
+ name: webhookDocument.summary || webhookDocument.name,
587
+ tags: ((_b = webhookDocument.tags) === null || _b === void 0 ? void 0 : _b.map(tag => tag.name)) || [],
588
+ });
589
+ }
459
590
  else if (match.type === NodeTypes.Model) {
460
591
  const schemaDocument = get__default["default"](document, jsonPath);
461
592
  const parsedUri = uri.replace(OAS_MODEL_REGEXP, 'schemas/');
@@ -528,9 +659,10 @@ const propsAreWithDocument = (props) => {
528
659
  return props.hasOwnProperty('apiDescriptionDocument');
529
660
  };
530
661
  const APIImpl = props => {
531
- const { layout, apiDescriptionUrl = '', logo, hideTryIt, hideSchemas, hideInternal, hideExport, tryItCredentialsPolicy, tryItCorsProxy, maxRefDepth, } = props;
662
+ const { layout = 'sidebar', apiDescriptionUrl = '', logo, hideTryIt, hideSchemas, hideInternal, hideExport, tryItCredentialsPolicy, tryItCorsProxy, maxRefDepth, } = props;
532
663
  const location = reactRouterDom.useLocation();
533
664
  const apiDescriptionDocument = propsAreWithDocument(props) ? props.apiDescriptionDocument : undefined;
665
+ const { isResponsiveLayoutEnabled } = elementsCore.useResponsiveLayout();
534
666
  const { data: fetchedDocument, error } = reactQuery.useQuery([apiDescriptionUrl], () => fetch(apiDescriptionUrl).then(res => {
535
667
  if (res.ok) {
536
668
  return res.text();
@@ -556,7 +688,10 @@ const APIImpl = props => {
556
688
  return (React__namespace.createElement(mosaic.Flex, { justify: "center", alignItems: "center", w: "full", minH: "screen" },
557
689
  React__namespace.createElement(elementsCore.NonIdealState, { title: "Failed to parse OpenAPI file", description: "Please make sure your OpenAPI file is valid and try again" })));
558
690
  }
559
- return (React__namespace.createElement(elementsCore.InlineRefResolverProvider, { document: parsedDocument, maxRefDepth: maxRefDepth }, layout === 'stacked' ? (React__namespace.createElement(APIWithStackedLayout, { serviceNode: serviceNode, hideTryIt: hideTryIt, hideExport: hideExport, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy, location: location })) : (React__namespace.createElement(APIWithSidebarLayout, { logo: logo, serviceNode: serviceNode, hideTryIt: hideTryIt, hideSchemas: hideSchemas, hideInternal: hideInternal, hideExport: hideExport, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy }))));
691
+ return (React__namespace.createElement(elementsCore.InlineRefResolverProvider, { document: parsedDocument, maxRefDepth: maxRefDepth },
692
+ layout === 'stacked' && (React__namespace.createElement(APIWithStackedLayout, { serviceNode: serviceNode, hideTryIt: hideTryIt, hideExport: hideExport, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy, location: location })),
693
+ layout === 'sidebar' && (React__namespace.createElement(APIWithSidebarLayout, { logo: logo, serviceNode: serviceNode, hideTryIt: hideTryIt, hideSchemas: hideSchemas, hideInternal: hideInternal, hideExport: hideExport, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy })),
694
+ layout === 'responsive' && (React__namespace.createElement(APIWithResponsiveSidebarLayout, { logo: logo, serviceNode: serviceNode, hideTryIt: hideTryIt, hideSchemas: hideSchemas, hideInternal: hideInternal, hideExport: hideExport, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy, compact: isResponsiveLayoutEnabled }))));
560
695
  };
561
696
  const API = flow__default["default"](elementsCore.withRouter, elementsCore.withStyles, elementsCore.withPersistenceBoundary, elementsCore.withMosaicProvider, elementsCore.withQueryClientProvider)(APIImpl);
562
697