@redocly/theme 0.1.27 → 0.1.28

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.
Files changed (110) hide show
  1. package/Button/Button.js +3 -3
  2. package/CodeBlock/CodeBlock.js +1 -1
  3. package/CopyButton/CopyButton.js +17 -1
  4. package/Footer/Footer.js +2 -1
  5. package/Footer/FooterColumn.js +1 -1
  6. package/Footer/FooterColumns.d.ts +2 -2
  7. package/Footer/FooterColumns.js +1 -1
  8. package/JsonViewer/JsonViewer.d.ts +2 -0
  9. package/JsonViewer/JsonViewer.js +53 -22
  10. package/Markdown/Admonition.js +1 -1
  11. package/Markdown/CodeSample/CodeSample.js +17 -1
  12. package/Markdown/Heading.js +1 -1
  13. package/Markdown/Mermaid.js +1 -1
  14. package/Markdown/Sup.d.ts +2 -0
  15. package/Markdown/Sup.js +19 -0
  16. package/Markdown/Tabs/Tabs.js +17 -1
  17. package/Markdown/index.d.ts +4 -3
  18. package/Markdown/index.js +4 -3
  19. package/Navbar/MobileNavbarDropdown.d.ts +8 -0
  20. package/Navbar/MobileNavbarDropdown.js +21 -0
  21. package/Navbar/MobileNavbarItem.d.ts +15 -0
  22. package/Navbar/MobileNavbarItem.js +102 -0
  23. package/Navbar/MobileNavbarMenu.d.ts +6 -0
  24. package/Navbar/MobileNavbarMenu.js +32 -0
  25. package/Navbar/MobileNavbarMenuButton.d.ts +4 -0
  26. package/Navbar/MobileNavbarMenuButton.js +19 -0
  27. package/Navbar/Navbar.js +26 -4
  28. package/Navbar/NavbarDropdown.js +1 -1
  29. package/Navbar/NavbarItem.d.ts +9 -3
  30. package/Navbar/NavbarItem.js +9 -9
  31. package/Navbar/NavbarMenu.js +3 -2
  32. package/PageNavigation/NextPageLink.js +4 -4
  33. package/PageNavigation/PreviousPageLink.js +4 -4
  34. package/Panel/PanelComponent.js +18 -2
  35. package/Search/Autocomplete.js +18 -2
  36. package/Search/utils.js +17 -1
  37. package/Sidebar/SidebarLayout.js +17 -1
  38. package/SourceCode/SourceCode.d.ts +10 -3
  39. package/SourceCode/SourceCode.js +10 -5
  40. package/TableOfContent/TableOfContent.js +4 -4
  41. package/globalStyle.js +2 -2
  42. package/hooks/useActiveHeading.js +17 -1
  43. package/hooks/useActiveSectionId.js +17 -1
  44. package/hooks/useControl.js +17 -1
  45. package/hooks/useMobileMenu.js +17 -1
  46. package/hooks/useNavbarHeight.js +17 -1
  47. package/package.json +1 -1
  48. package/src/Button/Button.tsx +5 -1
  49. package/src/CodeBlock/CodeBlock.ts +12 -0
  50. package/src/Footer/Footer.tsx +4 -3
  51. package/src/Footer/FooterColumn.tsx +3 -1
  52. package/src/Footer/FooterColumns.tsx +3 -3
  53. package/src/JsonViewer/JsonViewer.tsx +55 -40
  54. package/src/Markdown/Admonition.tsx +1 -1
  55. package/src/Markdown/Heading.tsx +1 -1
  56. package/src/Markdown/Mermaid.tsx +1 -1
  57. package/src/Markdown/Sup.tsx +8 -0
  58. package/src/Markdown/index.ts +4 -3
  59. package/src/Navbar/MobileNavbarDropdown.tsx +37 -0
  60. package/src/Navbar/MobileNavbarItem.tsx +116 -0
  61. package/src/Navbar/MobileNavbarMenu.tsx +106 -0
  62. package/src/Navbar/MobileNavbarMenuButton.tsx +13 -0
  63. package/src/Navbar/Navbar.tsx +11 -3
  64. package/src/Navbar/NavbarDropdown.tsx +1 -1
  65. package/src/Navbar/NavbarItem.tsx +9 -8
  66. package/src/Navbar/NavbarMenu.tsx +9 -4
  67. package/src/PageNavigation/NextPageLink.tsx +3 -3
  68. package/src/PageNavigation/PreviousPageLink.tsx +3 -3
  69. package/src/SourceCode/SourceCode.tsx +32 -5
  70. package/src/TableOfContent/TableOfContent.tsx +3 -3
  71. package/src/globalStyle.ts +2 -0
  72. package/src/types/portal/src/client/app/Sidebar/types.d.ts +2 -1
  73. package/src/types/portal/src/server/constants.d.ts +2 -0
  74. package/src/types/portal/src/server/dev-server/types.d.ts +22 -0
  75. package/src/types/portal/src/server/plugins/markdown/types.d.ts +15 -5
  76. package/src/types/portal/src/server/plugins/portal-config/get-frontmatter-keys-to-resolve.d.ts +2 -0
  77. package/src/types/portal/src/server/plugins/portal-config/types.d.ts +6 -2
  78. package/src/types/portal/src/server/plugins/reference-docs/utils.d.ts +26 -0
  79. package/src/types/portal/src/server/plugins/types.d.ts +29 -12
  80. package/src/types/portal/src/server/store.d.ts +16 -16
  81. package/src/types/portal/src/server/utils/fs.d.ts +2 -1
  82. package/src/types/portal/src/server/utils/index.d.ts +1 -0
  83. package/src/types/portal/src/server/utils/paths.d.ts +4 -0
  84. package/src/types/portal/src/server/utils/rbac.d.ts +15 -0
  85. package/src/types/portal/src/shared/constants.d.ts +7 -0
  86. package/src/types/portal/src/shared/models/config.d.ts +24 -12
  87. package/src/types/portal/src/shared/types.d.ts +17 -4
  88. package/src/types/portal/src/shared/urls.d.ts +1 -1
  89. package/src/types/portal/src/shared/utils.d.ts +2 -0
  90. package/src/ui/Burger.tsx +36 -0
  91. package/src/ui/Flex.tsx +1 -0
  92. package/src/utils/args-typecheck.ts +9 -0
  93. package/src/utils/highlight.ts +11 -0
  94. package/src/utils/index.ts +1 -0
  95. package/src/utils/jsonToHtml.ts +171 -59
  96. package/ui/Burger.d.ts +8 -0
  97. package/ui/Burger.js +23 -0
  98. package/ui/Dropdown.js +17 -1
  99. package/ui/Flex.js +1 -1
  100. package/ui/UniversalLink.js +17 -1
  101. package/utils/args-typecheck.d.ts +2 -0
  102. package/utils/args-typecheck.js +13 -0
  103. package/utils/highlight.d.ts +1 -0
  104. package/utils/highlight.js +12 -1
  105. package/utils/index.d.ts +1 -0
  106. package/utils/index.js +1 -0
  107. package/utils/jsonToHtml.d.ts +4 -1
  108. package/utils/jsonToHtml.js +287 -83
  109. package/utils/media-css.js +40 -3
  110. package/utils/theme-helpers.js +56 -9
@@ -1,13 +1,9 @@
1
1
  import { Plugin } from 'esbuild';
2
2
  import { JSONIndex, SearchDocument, SearchData } from '../shared/models';
3
+ import type { PageStaticData } from '../shared/types.js';
3
4
  import type { RouteDetails, Redirect } from './plugins/types.js';
4
- import type { GlobalData, GlobalConfig, RbacConfig } from './plugins/portal-config/types';
5
- export declare class Template {
6
- pathOrThemePath: string;
7
- id: string;
8
- hash: string;
9
- constructor(pathOrThemePath: string, id: string);
10
- }
5
+ import type { GlobalData, GlobalConfig } from './plugins/portal-config/types';
6
+ import type { ContentProvider } from './content/content-provider';
11
7
  export declare class Store {
12
8
  contentDir: string;
13
9
  outdir: string;
@@ -18,10 +14,11 @@ export declare class Store {
18
14
  searchDocuments: SearchDocument[];
19
15
  searchIndex: JSONIndex;
20
16
  sharedData: Map<string, unknown>;
21
- templates: Map<string, Template>;
17
+ routesStaticDataCache: Map<string, PageStaticData>;
18
+ templates: Map<string, string>;
19
+ serverPropsGetters: Map<string, string>;
22
20
  esbuildPlugins: Plugin[];
23
21
  listeners: Map<string, Set<Function>>;
24
- rbacConfig: RbacConfig;
25
22
  globalData: GlobalData;
26
23
  cliOptions: Record<string, string>;
27
24
  globalConfig: GlobalConfig;
@@ -34,19 +31,22 @@ export declare class Store {
34
31
  on(type: 'global-data-updated', cb: (pathname: string, data: Record<string, unknown>) => void): void;
35
32
  runListeners<T extends Array<unknown>>(type: 'template-updated' | 'global-data-updated' | 'shared-data-updated' | 'search-data-updated' | 'routes-updated' | 'build-updated', ...args: T): void;
36
33
  addEsbuildPlugin(plugin: Plugin): void;
37
- setGlobalData(data: GlobalData): void;
34
+ setGlobalData: (data: GlobalData) => void;
38
35
  setSearchData(data: SearchData): void;
39
36
  setCliOptions(data: Record<string, string>): void;
40
37
  setGlobalConfig(data: Partial<GlobalConfig>): void;
41
- setRbacConfig(rbacConfig: RbacConfig): void;
38
+ getRbacPermissionsByRole(role: string): string[] | undefined;
39
+ getGlobalConfig<T = unknown>(key: string): T;
42
40
  addRedirect(from: string, to: Redirect): void;
43
41
  createSharedData: (id: string, data: unknown) => string;
44
42
  addRouteSharedData: (routeSlug: string, dataKey: string, dataId: string) => void;
45
43
  addRoute: (route: RouteDetails) => void;
46
- getRouteByFsPath: (relativePath: string) => RouteDetails | undefined;
47
- getRouteBySlug: (slug: string) => RouteDetails | undefined;
48
- getAllRoutes: () => RouteDetails[];
49
- getTemplate: (id: string) => Template | undefined;
50
- createTemplate: (template: Template) => Template;
44
+ getRouteByFsPath: (relativePath: string) => RouteDetails<PageStaticData, import("../shared/types.js").PageProps> | undefined;
45
+ getRouteBySlug: (slug: string) => RouteDetails<PageStaticData, import("../shared/types.js").PageProps> | undefined;
46
+ getAllRoutes: () => RouteDetails<PageStaticData, import("../shared/types.js").PageProps>[];
47
+ getTemplate: (id: string) => string | undefined;
48
+ createTemplate: (id: string, importPath: string) => string;
49
+ registerServerPropsGetter: (id: string, importPath: string) => string;
50
+ resolveRouteStaticData(route: RouteDetails, contentProvider: ContentProvider): Promise<{}>;
51
51
  clear: () => void;
52
52
  }
@@ -1,4 +1,5 @@
1
1
  export declare function readFileAsStringSync(filePath: string): string | null | undefined;
2
2
  export declare function readFileNames(dir: string): string[];
3
- export declare function copyStaticFile(absolutePath: string, outdir: string): string;
3
+ export declare function copyStaticFile(absolutePath: string, outdir: string): string | undefined;
4
+ export declare function copyFolderRecursiveSync(source: string, target: string): void;
4
5
  export declare function ensureDir(path: string): string;
@@ -3,3 +3,4 @@ export * from './crypto.js';
3
3
  export * from './fs.js';
4
4
  export * from './reporter/reporter.js';
5
5
  export * from './paths.js';
6
+ export * from './rbac.js';
@@ -1,4 +1,6 @@
1
+ import { OpenAPIDefinition } from '@redocly/reference-docs';
1
2
  import type { ContentProvider } from '../content/content-provider.js';
3
+ import { Route } from '../dev-server/types';
2
4
  export declare function getDataFromRelativePath(relativePath: string): {
3
5
  pathVersion: string | null;
4
6
  basePath: string;
@@ -11,3 +13,5 @@ export declare function excludeVersion<T extends string[] | string>(arg: T): {
11
13
  version?: string;
12
14
  data: T;
13
15
  };
16
+ export declare function getParamsFromPath(path: string, route: Route): Record<string, string>;
17
+ export declare function getDefinitionFiles(contentProvider: ContentProvider): Promise<Map<string, OpenAPIDefinition>>;
@@ -0,0 +1,15 @@
1
+ import { Store } from '../store';
2
+ import { PERMISSION_PROP_NAME } from '../../shared/constants.js';
3
+ export declare function canAccessResource<T extends {
4
+ [PERMISSION_PROP_NAME]?: string;
5
+ }>(resource: T, permissions: Set<string>): boolean;
6
+ export declare function getPermissionsForRoles(store: Pick<Store, 'getRbacPermissionsByRole'>, roles: string[]): Set<string>;
7
+ export declare function getUserRolesFromCookies(cookies?: string): string[];
8
+ /**
9
+ * Maps the input deep and removes objects that have a 'permission' prop
10
+ * but do not match the input permissions list
11
+ * @param input can be object, array, string or any other value
12
+ * @param permissions
13
+ * @returns
14
+ */
15
+ export declare function filterDataByAccessDeep(input: any, permissions: Set<string>): any;
@@ -1,10 +1,17 @@
1
1
  export declare const RUNTIME_RESOURCES_DIR = "runtime";
2
2
  export declare const REDOC_OVERVIEW_ITEM_ID = "overview";
3
3
  export declare const PORTAL_CUSTOM_THEMES_FOLDER = "@theme";
4
+ export declare const DEFAULT_THEME_NAME = "@redocly/theme";
4
5
  export declare const CUSTOM_MARKDOC_OPTIONS_PATH = "markdoc";
5
6
  export declare const CUSTOM_MARKDOC_TAGS_PATH = "tags";
6
7
  export declare const USER_THEME_ALIAS = "@theme";
8
+ export declare const PUBLIC_ASSETS_FOLDER = "/assets";
7
9
  export declare const PUBLIC_STATIC_FOLDER = "/static";
10
+ export declare const THEME_ASSETS_FOLDER = "theme-assets";
11
+ export declare const PERMISSION_PROP_NAME = "redocly::permission";
12
+ export declare const OIDC_CALLBACK_ROUTE_PATH = "/_auth/oidc";
13
+ export declare const DEFAULT_OIDC_SCOPES = "openid";
8
14
  export declare enum FsErrors {
9
15
  NotExist = "ENOENT"
10
16
  }
17
+ export declare const INTERNALLY_IGNORED_FILES: string[];
@@ -1,9 +1,28 @@
1
- import { RawNavItem, LogoConfig, ResolvedNavItem } from '../types';
1
+ import { RawNavItem, LogoConfig } from '../types';
2
+ declare type RawNavConfig = RawNavItem[] | Record<string, RawNavItem[] | {
3
+ items: RawNavItem[];
4
+ } | string>;
5
+ export declare type AuthProviderConfig = {
6
+ type: string;
7
+ configurationUrl: string;
8
+ clientId: string;
9
+ clientSecret: string;
10
+ rolesClaimName: string;
11
+ scopes?: Array<string>;
12
+ };
13
+ export declare type AuthIdpsConfig = {
14
+ main: AuthProviderConfig;
15
+ };
16
+ export declare type AuthConfig = {
17
+ idps: AuthIdpsConfig;
18
+ };
2
19
  export interface PortalConfig extends Record<string, any> {
3
- nav?: RawNavItem[];
20
+ nav?: RawNavConfig;
4
21
  logo?: LogoConfig;
5
22
  themes?: RawTheme[];
6
- footer?: FooterConfig;
23
+ footer?: RawNavConfig;
24
+ auth?: AuthConfig;
25
+ frontmatterKeysToResolve?: string[];
7
26
  }
8
27
  export interface RawTheme {
9
28
  name: string;
@@ -11,18 +30,11 @@ export interface RawTheme {
11
30
  }
12
31
  export interface ResolvedTheme extends RawTheme {
13
32
  stylesFile?: string;
33
+ path: string;
14
34
  markdoc: {
15
35
  nodesFiles: string[];
16
36
  tagsFiles: string[];
17
37
  componentsFile?: string;
18
38
  };
19
39
  }
20
- export interface FooterConfig {
21
- copyrightText?: string;
22
- columns?: FooterColumn[];
23
- }
24
- export interface FooterColumn {
25
- group: string;
26
- label?: string;
27
- items: ResolvedNavItem[];
28
- }
40
+ export {};
@@ -1,4 +1,5 @@
1
1
  /// <reference types="react" />
2
+ import { PERMISSION_PROP_NAME } from './constants';
2
3
  export declare type ResolvedNavLinkItem = {
3
4
  type: 'link';
4
5
  link: string;
@@ -12,6 +13,7 @@ export declare type ResolvedNavLinkItem = {
12
13
  separatorLine?: boolean;
13
14
  routeSlug?: string;
14
15
  active?: boolean;
16
+ [PERMISSION_PROP_NAME]?: string;
15
17
  };
16
18
  export declare type ResolvedNavGroupItem = {
17
19
  type: 'group';
@@ -25,6 +27,7 @@ export declare type ResolvedNavGroupItem = {
25
27
  separatorLine?: boolean;
26
28
  routeSlug?: string;
27
29
  active?: boolean;
30
+ [PERMISSION_PROP_NAME]?: string;
28
31
  };
29
32
  export declare type ResolvedNavItem = ResolvedNavLinkItem | ResolvedNavGroupItem | {
30
33
  type: 'separator';
@@ -35,6 +38,7 @@ export declare type ResolvedNavItem = ResolvedNavLinkItem | ResolvedNavGroupItem
35
38
  separatorLine?: boolean;
36
39
  link?: undefined;
37
40
  items?: ResolvedNavItem[];
41
+ [PERMISSION_PROP_NAME]?: string;
38
42
  } | {
39
43
  type: 'error';
40
44
  version?: string;
@@ -43,23 +47,30 @@ export declare type ResolvedNavItem = ResolvedNavLinkItem | ResolvedNavGroupItem
43
47
  label: string;
44
48
  link?: undefined;
45
49
  items?: ResolvedNavItem[];
50
+ [PERMISSION_PROP_NAME]?: string;
46
51
  };
47
52
  export declare type ResolvedNavItemWithLink = (ResolvedNavLinkItem | ResolvedNavGroupItem) & {
48
53
  link: string;
49
54
  };
50
55
  export declare type ResolvedSidebar = ResolvedNavItem[];
51
56
  export interface PageProps {
52
- seo: {
57
+ seo?: {
53
58
  title: string;
54
59
  };
55
- versions?: Version[];
56
- frontmatter?: Omit<PageProps, 'frontmatter'>;
60
+ frontmatter?: Omit<PageProps, 'frontmatter'> & {
61
+ settings?: any;
62
+ };
63
+ [k: string]: any;
64
+ }
65
+ export interface PageStaticData {
66
+ props?: PageProps;
57
67
  [k: string]: unknown;
58
68
  }
59
69
  export interface PageData {
60
70
  templateId: string;
61
71
  sharedDataIds: Record<string, string>;
62
72
  props: PageProps;
73
+ versions?: Version[];
63
74
  }
64
75
  export interface CachedPageData extends PageData {
65
76
  Template: React.ComponentType<{
@@ -117,6 +128,8 @@ export declare type VersionedFolderConfig = {
117
128
  versions: VersionConfigItem[];
118
129
  hasVersionsConfig?: boolean;
119
130
  };
120
- export declare type NavGroup = ResolvedNavItem[] | undefined | string;
131
+ export declare type NavGroup = ResolvedNavItem[] | undefined | string | boolean | number;
121
132
  export declare type NavGroupRecord = Record<string, NavGroup>;
122
133
  export declare type ResolvedConfigLinks = NavGroup | NavGroupRecord;
134
+ export declare type RawNavConfigItem = RawNavItem | RawNavItem[] | string | undefined | boolean | number;
135
+ export declare type RawNavConfig = RawNavConfigItem | Record<string, RawNavConfigItem>;
@@ -1,10 +1,10 @@
1
1
  export declare function getPageDataUrl(normalizedRoute: string): string;
2
2
  export declare function getClientPageDataUrl(normalizedRoute: string): string;
3
- export declare function getSidebarUrl(id: string): string;
4
3
  export declare function getSharedDataUrl(id: string): string;
5
4
  export declare const GLOBAL_DATA_URL = "/app-data.json";
6
5
  export declare const SEARCH_DATA_URL = "/search-data.json";
7
6
  export declare const SEARCH_INDEX_URL = "/search-index.json";
7
+ export declare const EJECT_COMPONENT_URL = "/eject-component";
8
8
  export declare function combineUrls(baseURL: string, ...relativeURLs: string[]): string;
9
9
  export declare function withPathPrefix(url: string): string;
10
10
  export declare function withoutHash(url: string): string;
@@ -14,3 +14,5 @@ export declare function getGlobMatcher(glob: Glob, options?: {}): (file: string)
14
14
  export declare function isTag(value?: any): value is Tag;
15
15
  export declare function isNode(value?: any): value is Node;
16
16
  export declare const getCssPropertyValue: (name: string) => string;
17
+ export declare function normalizeRoute(route: string): string;
18
+ export declare function isPrimitive(arg: any): arg is string | boolean | number;
@@ -0,0 +1,36 @@
1
+ import React, { MouseEventHandler } from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ export interface BurgerProps {
5
+ onClick: MouseEventHandler;
6
+ className?: string;
7
+ }
8
+
9
+ function BurgerComponent({ onClick, className }: BurgerProps): JSX.Element {
10
+ return (
11
+ <Button onClick={onClick} className={className} data-component-name="ui/Burger">
12
+ <Line></Line>
13
+ <Line></Line>
14
+ <Line></Line>
15
+ </Button>
16
+ );
17
+ }
18
+
19
+ const Button = styled.button`
20
+ all: unset;
21
+ max-width: 30px;
22
+ width: 100%;
23
+ min-width: 10px;
24
+ display: block;
25
+ cursor: pointer;
26
+ `;
27
+
28
+ const Line = styled.div`
29
+ margin: 4px 0;
30
+ width: 100%;
31
+ height: 2px;
32
+ background: #fff;
33
+ border-radius: 1px;
34
+ `;
35
+
36
+ export const Burger = styled(BurgerComponent)``;
package/src/ui/Flex.tsx CHANGED
@@ -8,6 +8,7 @@ export interface FlexProps extends FlexboxProps, WidthProps {}
8
8
 
9
9
  export const Flex = styled(Box).attrs({ 'data-component-name': 'ui/Flex' })<FlexProps>`
10
10
  display: flex;
11
+ flex-wrap: wrap;
11
12
  ${flexbox}
12
13
  ${width}
13
14
  `;
@@ -0,0 +1,9 @@
1
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
2
+ export function isEmptyArray(items: any): boolean {
3
+ return Array.isArray(items) && !items?.length;
4
+ }
5
+
6
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
7
+ export function isPrimitive(arg: any): boolean {
8
+ return ['string', 'boolean', 'number'].includes(typeof arg);
9
+ }
@@ -22,6 +22,7 @@ import 'prismjs/components/prism-swift.js';
22
22
  import 'prismjs/components/prism-graphql.js';
23
23
 
24
24
  const DEFAULT_LANG = 'clike';
25
+ const NEW_LINE_EXP = /\n(?!$)/g;
25
26
 
26
27
  Prism.languages.insertBefore(
27
28
  'javascript',
@@ -79,3 +80,13 @@ export function highlight(source: string | number | boolean, lang: string = DEFA
79
80
  }
80
81
  return Prism.highlight(source.toString(), grammar, lang);
81
82
  }
83
+
84
+ export function addLineNumbers(highlightedCode: string, start = 1): string {
85
+ const codeLines = highlightedCode.split(NEW_LINE_EXP);
86
+
87
+ return codeLines
88
+ .map((line, i) => {
89
+ return `<span class="code-line" data-line-number=${start + i}>${line}</span>`;
90
+ })
91
+ .join('\n');
92
+ }
@@ -5,3 +5,4 @@ export * from './jsonToHtml';
5
5
  export * from './media-css';
6
6
  export * from './theme-helpers';
7
7
  export * from './class-names';
8
+ export * from './args-typecheck';
@@ -1,12 +1,21 @@
1
1
  // Moved from reference-docs
2
- let level = 1;
2
+ let level = 0;
3
+ let line = 1;
3
4
 
4
- export function jsonToHTML(json: JSON | Record<string, unknown>, maxExpandLevel: number): string {
5
- level = 1;
5
+ type ValueContext = 'array' | 'object' | 'root';
6
+ type LineGenerator = Generator<string, string | void, void>;
7
+ type JsonValue = JSON | Record<string, unknown>;
8
+
9
+ const ellipsis = '<span class="ellipsis"></span>';
10
+ const colon = '<span class="colon">: </span>';
11
+
12
+ export function jsonToHTML(json: JsonValue, maxExpandLevel: number, startLine = 1): string {
13
+ level = 0;
14
+ line = startLine;
6
15
  let output = '';
7
16
  output += '<div data-cy="json-sample" class="redoc-json">';
8
17
  output += '<code>';
9
- output += valueToHTML(json, maxExpandLevel);
18
+ output += getLines(json, maxExpandLevel);
10
19
  output += '</code>';
11
20
  output += '</div>';
12
21
  return output;
@@ -35,88 +44,191 @@ function punctuation(val: any) {
35
44
  return '<span class="token punctuation">' + val + '</span>';
36
45
  }
37
46
 
38
- function valueToHTML(value: any, maxExpandLevel: number) {
47
+ function collapser(maxExpandLevel: number): string {
48
+ const collapsed = level > maxExpandLevel ? 'expand' : 'collapse';
49
+
50
+ return `<button class="collapser" aria-label="${collapsed}"></button>`;
51
+ }
52
+
53
+ export function getLines(json: JsonValue, maxExpandLevel: number): string {
54
+ const collapsibleLines = [];
55
+ const lines = valueToHTML(json, maxExpandLevel, 'root');
56
+
57
+ let nextLine = lines.next();
58
+
59
+ while (!nextLine.done) {
60
+ collapsibleLines.push(nextLine.value);
61
+ nextLine = lines.next();
62
+ }
63
+
64
+ collapsibleLines.push(nextLine.value);
65
+
66
+ return collapsibleLines.join('\n');
67
+ }
68
+
69
+ function* valueToHTML(value: any, maxExpandLevel: number, context: ValueContext) {
39
70
  const valueType = typeof value;
40
- let output = '';
71
+
41
72
  if (value === undefined || value === null) {
42
- output += decorateWithSpan('null', 'token keyword');
73
+ return decorateWithSpan('null', 'token keyword');
43
74
  } else if (value && value.constructor === Array) {
75
+ const length = typeof value.length === 'number' ? value.length : 0;
76
+
77
+ if (!length) {
78
+ return punctuation('[ ]');
79
+ }
80
+
44
81
  level++;
45
- output += arrayToHTML(value, maxExpandLevel);
82
+
83
+ yield context === 'root'
84
+ ? `<span data-line="${line++}">${punctuation('[')}</span>`
85
+ : punctuation('[');
86
+
87
+ for (const line of arrayToHTML(value, length, maxExpandLevel)) {
88
+ yield line;
89
+ }
90
+
46
91
  level--;
92
+
93
+ return `<span data-line="${line++}">${punctuation(']')}</span>`;
47
94
  } else if (value && value.constructor === Date) {
48
- output += decorateWithSpan('"' + value.toISOString() + '"', 'token string');
95
+ return decorateWithSpan('"' + value.toISOString() + '"', 'token string');
49
96
  } else if (valueType === 'object') {
97
+ const keys = Object.keys(value);
98
+
99
+ if (!keys.length) {
100
+ return punctuation('{ }');
101
+ }
102
+
50
103
  level++;
51
- output += objectToHTML(value, maxExpandLevel);
104
+
105
+ yield context === 'root'
106
+ ? `<span data-line="${line++}">${punctuation('{')}</span>`
107
+ : punctuation('{');
108
+
109
+ for (const line of objectToHTML(value, keys, maxExpandLevel)) {
110
+ yield line;
111
+ }
112
+
52
113
  level--;
114
+
115
+ return `<span data-line="${line++}">${punctuation('}')}</span>`;
53
116
  } else if (valueType === 'number') {
54
- output += decorateWithSpan(value, 'token number');
117
+ return decorateWithSpan(value, 'token number');
55
118
  } else if (valueType === 'string') {
56
119
  if (/^(http|https):\/\/[^\s]+$/.test(value)) {
57
- output +=
120
+ return (
58
121
  decorateWithSpan('"', 'token string') +
59
- '<a href="' +
60
- encodeURI(value) +
61
- '">' +
122
+ `<a href="${encodeURI(value)}">` +
62
123
  htmlEncode(stringifyStringLiteral(value)) +
63
124
  '</a>' +
64
- decorateWithSpan('"', 'token string');
125
+ decorateWithSpan('"', 'token string')
126
+ );
65
127
  } else {
66
- output += decorateWithSpan('"' + stringifyStringLiteral(value) + '"', 'token string');
128
+ return decorateWithSpan('"' + stringifyStringLiteral(value) + '"', 'token string');
67
129
  }
68
130
  } else if (valueType === 'boolean') {
69
- output += decorateWithSpan(value, 'token boolean');
131
+ return decorateWithSpan(value, 'token boolean');
70
132
  }
133
+ }
71
134
 
72
- return output;
135
+ function getLineTemplate({
136
+ isFirstKey,
137
+ isLastKey,
138
+ nextLine,
139
+ context,
140
+ lineTemplate,
141
+ maxExpandLevel,
142
+ }: {
143
+ isFirstKey: boolean;
144
+ isLastKey: boolean;
145
+ nextLine: IteratorResult<string, string | undefined>;
146
+ maxExpandLevel: number;
147
+ lineTemplate: string;
148
+ context: 'array' | 'obj';
149
+ }) {
150
+ let lineValue = isFirstKey ? `${ellipsis}<span class="${context} collapsible">` : '';
151
+ // Create hoverable
152
+ lineValue += `<span data-line="${line++}" class="${
153
+ level > maxExpandLevel ? 'hoverable collapsed' : 'hoverable'
154
+ }">`;
155
+ // Add collapser button if item has nested lines
156
+ lineValue += (!nextLine.done && collapser(maxExpandLevel)) || '';
157
+ lineValue += lineTemplate;
158
+ lineValue += nextLine.value;
159
+ // Add punctuation in case we dont have more nested lines and it is not last item
160
+ lineValue += nextLine.done && !isLastKey ? punctuation(',') : '';
161
+ // Close hoverable if we dont have nested lines
162
+ lineValue += nextLine.done ? '</span>' : '';
163
+ // Close collapsible if value is single line and we are on last item
164
+ lineValue += nextLine.done && isLastKey ? '</span>' : '';
165
+
166
+ return lineValue;
73
167
  }
74
168
 
75
- function arrayToHTML(json: any, maxExpandLevel: number) {
76
- const collapsed = level > maxExpandLevel ? 'collapsed' : '';
77
- let output = `<button class="collapser" aria-label="${
78
- level > maxExpandLevel + 1 ? 'expand' : 'collapse'
79
- }"></button>${punctuation('[')}<span class="ellipsis"></span><ul class="array collapsible">`;
80
- let hasContents = false;
81
- const length = json.length;
82
- for (let i = 0; i < length; i++) {
83
- hasContents = true;
84
- output += '<li><div class="hoverable ' + collapsed + '">';
85
- output += valueToHTML(json[i], maxExpandLevel);
86
- if (i < length - 1) {
87
- output += ',';
169
+ function* arrayToHTML(array: any[], arrayLength: number, maxExpandLevel: number): LineGenerator {
170
+ for (let i = 0; i < arrayLength; i++) {
171
+ const lines = valueToHTML(array[i], maxExpandLevel, 'array');
172
+ const isFirstKey = i === 0;
173
+ const isLastKey = i === arrayLength - 1;
174
+ let nextLine = lines.next();
175
+
176
+ yield getLineTemplate({
177
+ context: 'array',
178
+ isFirstKey,
179
+ isLastKey,
180
+ nextLine,
181
+ lineTemplate: '',
182
+ maxExpandLevel,
183
+ });
184
+
185
+ nextLine = lines.next();
186
+
187
+ while (!nextLine.done) {
188
+ yield nextLine.value;
189
+
190
+ nextLine = lines.next();
191
+ }
192
+
193
+ if (nextLine.value) {
194
+ // Close hoverable and collapsible (if last array item)
195
+ yield `${nextLine.value}${i < arrayLength - 1 ? punctuation(',') : '</span>'}</span>`;
88
196
  }
89
- output += '</div></li>';
90
- }
91
- output += `</ul>${punctuation(']')}`;
92
- if (!hasContents) {
93
- output = punctuation('[ ]');
94
197
  }
95
- return output;
96
198
  }
97
199
 
98
- function objectToHTML(json: any, maxExpandLevel: number) {
99
- const collapsed = level > maxExpandLevel ? 'collapsed' : '';
100
- const keys = Object.keys(json);
101
- const length = keys.length;
102
- let output = `<button class="collapser" aria-label="${
103
- level > maxExpandLevel + 1 ? 'expand' : 'collapse'
104
- }"></button>${punctuation('{')}<span class="ellipsis"></span><ul class="obj collapsible">`;
105
- let hasContents = false;
106
- for (let i = 0; i < length; i++) {
200
+ function* objectToHTML(
201
+ object: Record<any, any>,
202
+ keys: any[],
203
+ maxExpandLevel: number,
204
+ ): LineGenerator {
205
+ for (let i = 0; i < keys.length; i++) {
107
206
  const key = keys[i];
108
- hasContents = true;
109
- output += '<li><div class="hoverable ' + collapsed + '">';
110
- output += '<span class="property token string">"' + htmlEncode(key) + '"</span>: ';
111
- output += valueToHTML(json[key], maxExpandLevel);
112
- if (i < length - 1) {
113
- output += punctuation(',');
207
+ const lines = valueToHTML(object[key], maxExpandLevel, 'object');
208
+ const isFirstKey = i === 0;
209
+ const isLastKey = i === keys.length - 1;
210
+
211
+ let nextLine = lines.next();
212
+
213
+ yield getLineTemplate({
214
+ context: 'obj',
215
+ isFirstKey,
216
+ isLastKey,
217
+ nextLine,
218
+ lineTemplate: `<span class="property token string">"${htmlEncode(key)}"</span>${colon}`,
219
+ maxExpandLevel,
220
+ });
221
+
222
+ nextLine = lines.next();
223
+
224
+ while (!nextLine.done) {
225
+ yield nextLine.value;
226
+ nextLine = lines.next();
227
+ }
228
+
229
+ if (nextLine.value) {
230
+ // Close hoverable and collapsible (if last field)
231
+ yield `${nextLine.value}${isLastKey ? '</span>' : punctuation(',')}</span>`;
114
232
  }
115
- output += '</div></li>';
116
- }
117
- output += `</ul>${punctuation('}')}`;
118
- if (!hasContents) {
119
- output = punctuation('{ }');
120
233
  }
121
- return output;
122
234
  }
package/ui/Burger.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { MouseEventHandler } from 'react';
2
+ export interface BurgerProps {
3
+ onClick: MouseEventHandler;
4
+ className?: string;
5
+ }
6
+ declare function BurgerComponent({ onClick, className }: BurgerProps): JSX.Element;
7
+ export declare const Burger: import("styled-components").StyledComponent<typeof BurgerComponent, any, {}, never>;
8
+ export {};
package/ui/Burger.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
3
+ if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
4
+ return cooked;
5
+ };
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.Burger = void 0;
11
+ var react_1 = __importDefault(require("react"));
12
+ var styled_components_1 = __importDefault(require("styled-components"));
13
+ function BurgerComponent(_a) {
14
+ var onClick = _a.onClick, className = _a.className;
15
+ return (react_1.default.createElement(Button, { onClick: onClick, className: className, "data-component-name": "ui/Burger" },
16
+ react_1.default.createElement(Line, null),
17
+ react_1.default.createElement(Line, null),
18
+ react_1.default.createElement(Line, null)));
19
+ }
20
+ var Button = styled_components_1.default.button(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n all: unset;\n max-width: 30px;\n width: 100%;\n min-width: 10px;\n display: block;\n cursor: pointer;\n"], ["\n all: unset;\n max-width: 30px;\n width: 100%;\n min-width: 10px;\n display: block;\n cursor: pointer;\n"])));
21
+ var Line = styled_components_1.default.div(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n margin: 4px 0;\n width: 100%;\n height: 2px;\n background: #fff;\n border-radius: 1px;\n"], ["\n margin: 4px 0;\n width: 100%;\n height: 2px;\n background: #fff;\n border-radius: 1px;\n"])));
22
+ exports.Burger = (0, styled_components_1.default)(BurgerComponent)(templateObject_3 || (templateObject_3 = __makeTemplateObject([""], [""])));
23
+ var templateObject_1, templateObject_2, templateObject_3;