diff --git a/packages/docusaurus-plugin-redoc/src/index.ts b/packages/docusaurus-plugin-redoc/src/index.ts index 04a5a947..04564e14 100644 --- a/packages/docusaurus-plugin-redoc/src/index.ts +++ b/packages/docusaurus-plugin-redoc/src/index.ts @@ -24,7 +24,7 @@ import { PluginDirectUsageOptions, DEFAULT_OPTIONS, } from './options'; -import type { SpecPropsWithUrl, ApiDocProps } from './types/common'; +import type { SpecDataResult, ApiDocProps } from './types/common'; import { loadSpecWithConfig } from './loadSpec'; import { loadRedoclyConfig } from './loadRedoclyConfig'; @@ -128,7 +128,7 @@ export default function redocPlugin( throw new Error(`[Redocusaurus] Spec could not be parsed: ${spec}`); } - const data: SpecPropsWithUrl = { + const data: SpecDataResult = { url, themeId, // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx b/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx index e90b1b73..a278a4bf 100644 --- a/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/ApiSchema/ApiSchema.tsx @@ -3,17 +3,25 @@ import clsx from 'clsx'; import { ThemeProvider } from 'styled-components'; import '../../global'; import { SchemaDefinition } from 'redoc'; -import useSpec from '../../utils/useSpec'; -import { ApiSchemaProps as Props } from '../../types/common'; +import useSpec from '@theme/useSpec'; import '../Redoc/styles.css'; import './styles.css'; -const ApiSchema: React.FC = ({ +const ApiSchema: React.FC = ({ showExample, pointer, + id, + spec, + optionsOverrides, ...rest -}: Props): JSX.Element => { - const { store } = useSpec(rest); +}: ApiSchemaProps): JSX.Element => { + const { store } = useSpec( + { + id, + spec, + }, + optionsOverrides, + ); useEffect(() => { /** diff --git a/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx b/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx index 0d2149ba..17bf7280 100644 --- a/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/Redoc/Redoc.tsx @@ -2,11 +2,14 @@ import React from 'react'; import clsx from 'clsx'; import '../../global'; import { RedocStandalone } from 'redoc'; -import { RedocProps } from '../../types/common'; -import useSpecOptions from '../../utils/useSpecOptions'; +import useSpecOptions from '@theme/useSpecOptions'; import './styles.css'; import ServerRedoc from './ServerRedoc'; +function getIsExternalUrl(url = '') { + return ['http://', 'https://'].some((protocol) => url.startsWith(protocol)); +} + /*! * Redocusaurus * https://redocusaurus.vercel.app/ @@ -14,11 +17,10 @@ import ServerRedoc from './ServerRedoc'; * Released under the MIT License */ function Redoc(props: RedocProps): JSX.Element { - const { className, optionsOverrides, spec, url, themeId, isSpecFile } = props; + const { className, optionsOverrides, url, themeId } = props; const { options } = useSpecOptions(themeId, optionsOverrides); - const isDevMode = process.env.NODE_ENV === 'development'; - if ((isDevMode && isSpecFile === false) || !spec) { + if (getIsExternalUrl(url)) { return (
@@ -26,7 +28,7 @@ function Redoc(props: RedocProps): JSX.Element { ); } - return ; + return ; } export default Redoc; diff --git a/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx b/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx index c15c55c8..8d81ee0a 100644 --- a/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerRedoc.tsx @@ -1,9 +1,8 @@ import React from 'react'; import clsx from 'clsx'; import '../../global'; -import { Redoc as RedocComponent, RedocRawOptions } from 'redoc'; -import { SpecProps } from '../../types/common'; -import useSpec from '../../utils/useSpec'; +import { Redoc as RedocComponent } from 'redoc'; +import useSpec from '@theme/useSpec'; import { ServerStyles } from './Styles'; import './styles.css'; @@ -13,22 +12,22 @@ import './styles.css'; * (c) 2024 Rohit Gohri * Released under the MIT License */ -function ServerRedoc( - props: SpecProps & { - className?: string; - optionsOverrides?: RedocRawOptions; - }, -): JSX.Element { - const { className, optionsOverrides, ...specProps } = props; - const { store, darkThemeOptions, lightThemeOptions, hasLogo } = useSpec( - specProps, +function ServerRedoc(props: RedocProps): JSX.Element { + const { className, optionsOverrides, url, id, themeId } = props; + const { store, spec, darkThemeOptions, lightThemeOptions, hasLogo } = useSpec( + { + spec: props.spec, + themeId, + id, + }, optionsOverrides, ); return ( <> diff --git a/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerStyles.tsx b/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerStyles.tsx index 1969cdd5..bf1a3da2 100644 --- a/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerStyles.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/Redoc/ServerStyles.tsx @@ -2,6 +2,7 @@ import React from 'react'; import '../../global'; import useBaseUrl from '@docusaurus/useBaseUrl'; import { AppStore, Redoc, RedocRawOptions } from 'redoc'; +import type { OpenAPISpec } from 'redoc/typings/types'; // eslint-disable-next-line import/no-extraneous-dependencies import { renderToString } from 'react-dom/server'; import { ServerStyleSheet } from 'styled-components'; @@ -53,22 +54,26 @@ const prefixCssSelectors = function (rules: string, className: string) { const LIGHT_MODE_PREFIX = "html:not([data-theme='dark'])"; const DARK_MODE_PREFIX = "html([data-theme='dark'])"; +export type ServerStylesProps = { + spec: OpenAPISpec; + url?: string; + lightThemeOptions: RedocRawOptions; + darkThemeOptions: RedocRawOptions; +}; + export function ServerStyles({ - specProps, + spec, + url, lightThemeOptions, darkThemeOptions, -}: { - specProps: SpecProps; - lightThemeOptions: RedocRawOptions; - darkThemeOptions: RedocRawOptions; -}) { - const fullUrl = useBaseUrl(specProps.url, { absolute: true }); +}: ServerStylesProps) { + const fullUrl = useBaseUrl(url, { absolute: true }); const css = { light: '', dark: '', }; const lightSheet = new ServerStyleSheet(); - const lightStore = new AppStore(specProps.spec, fullUrl, lightThemeOptions); + const lightStore = new AppStore(spec, fullUrl, lightThemeOptions); renderToString( lightSheet.collectStyles(React.createElement(Redoc, { store: lightStore })), ); @@ -78,7 +83,7 @@ export function ServerStyles({ css.light = prefixCssSelectors(lightCss, LIGHT_MODE_PREFIX); const darkSheet = new ServerStyleSheet(); - const darkStore = new AppStore(specProps.spec, fullUrl, darkThemeOptions); + const darkStore = new AppStore(spec, fullUrl, darkThemeOptions); renderToString( darkSheet.collectStyles(React.createElement(Redoc, { store: darkStore })), ); diff --git a/packages/docusaurus-theme-redoc/src/theme/Redoc/Styles.tsx b/packages/docusaurus-theme-redoc/src/theme/Redoc/Styles.tsx index 7a3bf976..17a39d0c 100644 --- a/packages/docusaurus-theme-redoc/src/theme/Redoc/Styles.tsx +++ b/packages/docusaurus-theme-redoc/src/theme/Redoc/Styles.tsx @@ -1,15 +1,11 @@ import React from 'react'; import '../../global'; -import type { RedocRawOptions } from 'redoc'; +import type { ServerStylesProps } from './ServerStyles'; /** * Don't hydrate/replace server styles * @see https://github.com/facebook/react/issues/10923#issuecomment-338715787 */ -export function ServerStyles(_props: { - specProps: SpecProps; - lightThemeOptions: RedocRawOptions; - darkThemeOptions: RedocRawOptions; -}) { +export function ServerStyles(_props: ServerStylesProps) { return
; } diff --git a/packages/docusaurus-theme-redoc/src/theme/useSpec/index.ts b/packages/docusaurus-theme-redoc/src/theme/useSpec/index.ts new file mode 100644 index 00000000..30c5c48c --- /dev/null +++ b/packages/docusaurus-theme-redoc/src/theme/useSpec/index.ts @@ -0,0 +1,3 @@ +import { useSpec } from './useSpec'; + +export default useSpec; diff --git a/packages/docusaurus-theme-redoc/src/utils/useSpec.ts b/packages/docusaurus-theme-redoc/src/theme/useSpec/useSpec.ts similarity index 82% rename from packages/docusaurus-theme-redoc/src/utils/useSpec.ts rename to packages/docusaurus-theme-redoc/src/theme/useSpec/useSpec.ts index bf2385b1..a60f4e9d 100644 --- a/packages/docusaurus-theme-redoc/src/utils/useSpec.ts +++ b/packages/docusaurus-theme-redoc/src/theme/useSpec/useSpec.ts @@ -2,11 +2,10 @@ import { useMemo, useEffect } from 'react'; import useBaseUrl from '@docusaurus/useBaseUrl'; import useIsBrowser from '@docusaurus/useIsBrowser'; import { useColorMode } from '@docusaurus/theme-common'; -import '../global'; +import useSpecData from '@theme/useSpecData'; +import useSpecOptions from '@theme/useSpecOptions'; +import '../../global'; import { AppStore, RedocRawOptions } from 'redoc'; -import { SpecProps } from '../types/common'; -import useSpecOptions from './useSpecOptions'; -import useSpecData from './useSpecData'; // the current store singleton in the app's instance let currentStore: AppStore | null = null; @@ -17,11 +16,15 @@ let currentStore: AppStore | null = null; * (c) 2024 Rohit Gohri * Released under the MIT License */ -export default function useSpec( +export function useSpec( specInfo: SpecProps, optionsOverrides?: RedocRawOptions, -) { - const { spec, url, themeId } = useSpecData(specInfo); +): SpecResult { + const { spec, url, themeId } = useSpecData( + specInfo.id, + specInfo.spec, + specInfo.themeId, + ); const specOptions = useSpecOptions(themeId, optionsOverrides); const fullUrl = useBaseUrl(url, { absolute: true }); const isBrowser = useIsBrowser(); @@ -38,6 +41,7 @@ export default function useSpec( // @ts-expect-error extra prop hasLogo: !!spec.info?.['x-logo'], store: currentStore, + spec, }; }, [isBrowser, spec, fullUrl, specOptions]); diff --git a/packages/docusaurus-theme-redoc/src/theme/useSpecData/index.ts b/packages/docusaurus-theme-redoc/src/theme/useSpecData/index.ts new file mode 100644 index 00000000..da2a5330 --- /dev/null +++ b/packages/docusaurus-theme-redoc/src/theme/useSpecData/index.ts @@ -0,0 +1,3 @@ +import { useSpecData } from './useSpecData'; + +export default useSpecData; diff --git a/packages/docusaurus-theme-redoc/src/utils/useSpecData.ts b/packages/docusaurus-theme-redoc/src/theme/useSpecData/useSpecData.ts similarity index 72% rename from packages/docusaurus-theme-redoc/src/utils/useSpecData.ts rename to packages/docusaurus-theme-redoc/src/theme/useSpecData/useSpecData.ts index 4fb88f79..a6294d48 100644 --- a/packages/docusaurus-theme-redoc/src/utils/useSpecData.ts +++ b/packages/docusaurus-theme-redoc/src/theme/useSpecData/useSpecData.ts @@ -1,6 +1,6 @@ import { useAllPluginInstancesData } from '@docusaurus/useGlobalData'; import type { OpenAPISpec } from 'redoc/typings/types'; -import { SpecProps, SpecPropsWithUrl } from '../types/common'; +import { SpecDataResult } from '../../types/common'; export type ParsedSpec = OpenAPISpec; @@ -13,17 +13,23 @@ export type ParsedSpec = OpenAPISpec; * @param providedSpec spec data * @returns Spec Data of ID or first one if ID is not provided */ -export default function useSpecData(providedSpec: SpecProps): SpecPropsWithUrl { +export function useSpecData( + id?: string, + spec?: OpenAPISpec, + themeId?: string, +): SpecDataResult { const allData = useAllPluginInstancesData('docusaurus-plugin-redoc'); - if (providedSpec.spec) { + if (spec) { // return provided spec when already defined - return providedSpec as SpecPropsWithUrl; + return { + spec, + themeId, + }; } else { // retrieve spec from docusaurus conf - const id = providedSpec.id; const apiData = id ? allData?.[id as string] : Object.values(allData ?? {})?.[0]; - return apiData as SpecPropsWithUrl; + return apiData as SpecDataResult; } } diff --git a/packages/docusaurus-theme-redoc/src/theme/useSpecOptions/index.ts b/packages/docusaurus-theme-redoc/src/theme/useSpecOptions/index.ts new file mode 100644 index 00000000..a7fdb6ed --- /dev/null +++ b/packages/docusaurus-theme-redoc/src/theme/useSpecOptions/index.ts @@ -0,0 +1,3 @@ +import { useSpecOptions } from './useSpecOptions'; + +export default useSpecOptions; diff --git a/packages/docusaurus-theme-redoc/src/utils/useSpecOptions.ts b/packages/docusaurus-theme-redoc/src/theme/useSpecOptions/useSpecOptions.ts similarity index 92% rename from packages/docusaurus-theme-redoc/src/utils/useSpecOptions.ts rename to packages/docusaurus-theme-redoc/src/theme/useSpecOptions/useSpecOptions.ts index 676f5f67..3a867167 100644 --- a/packages/docusaurus-theme-redoc/src/utils/useSpecOptions.ts +++ b/packages/docusaurus-theme-redoc/src/theme/useSpecOptions/useSpecOptions.ts @@ -6,10 +6,10 @@ import { } from '@docusaurus/useGlobalData'; import { useColorMode } from '@docusaurus/theme-common'; import merge from 'lodash/merge'; -import '../global'; +import '../../global'; import { RedocRawOptions } from 'redoc'; -import { SpecProps } from '../types/common'; -import { GlobalData } from '../types/options'; +import { SpecProps } from '../../types/common'; +import { GlobalData } from '../../types/options'; /** * Redocusaurus @@ -17,7 +17,7 @@ import { GlobalData } from '../types/options'; * (c) 2024 Rohit Gohri * Released under the MIT License */ -export default function useSpecOptions( +export function useSpecOptions( themeId: SpecProps['themeId'] = 'theme-redoc', optionsOverrides?: RedocRawOptions, ) { diff --git a/packages/docusaurus-theme-redoc/src/types/common.ts b/packages/docusaurus-theme-redoc/src/types/common.ts index 3d7c3eca..03937907 100644 --- a/packages/docusaurus-theme-redoc/src/types/common.ts +++ b/packages/docusaurus-theme-redoc/src/types/common.ts @@ -1,14 +1,11 @@ import type { Props as LayoutProps } from '@theme/Layout'; -import type { ObjectDescriptionProps, RedocRawOptions } from 'redoc'; -import type { OpenAPISpec } from 'redoc/typings/types'; - -export type ParsedSpec = OpenAPISpec; +import type { ObjectDescriptionProps } from 'redoc'; export interface SpecProps { /** * Spec to use, already loaded previously */ - spec: ParsedSpec; + spec: import('redoc/typings/types').OpenAPISpec; /** * When spec not provided, load the spec from docusaurus config * fallback to first configuration if not provided @@ -20,23 +17,9 @@ export interface SpecProps { themeId?: string; } -export type SpecPropsWithUrl = Omit & { - /** - * Absolute path to the spec file used - */ - url?: string; -}; - -export type RedocProps = SpecProps & { - /** - * FIXME - incorrect name - should be isExternalUrl - */ - isSpecFile?: boolean; - className?: string; - optionsOverrides?: RedocRawOptions; +export type SpecDataResult = Omit & { /** - * External URL to load spec file from - * FIXME - incorrect name - should be externalUrl + * Public path to the spec file used, used by Redoc as download url */ url?: string; }; @@ -48,17 +31,6 @@ export interface MdxProps { */ id?: string; } -export type ApiSchemaProps = Omit< - ObjectDescriptionProps, - 'parser' | 'options' | 'schemaRef' -> & - MdxProps & { - /** - * Ref to the schema - */ - pointer: ObjectDescriptionProps['schemaRef']; - spec: ParsedSpec; - }; export type ApiDocProps = { specProps: SpecProps; diff --git a/packages/docusaurus-theme-redoc/src/types/modules.ts b/packages/docusaurus-theme-redoc/src/types/modules.ts index a4b38f75..7cf9b28d 100644 --- a/packages/docusaurus-theme-redoc/src/types/modules.ts +++ b/packages/docusaurus-theme-redoc/src/types/modules.ts @@ -1,19 +1,50 @@ interface SpecProps { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + /** + * Spec to use, already loaded previously + */ spec: import('redoc/typings/types').OpenAPISpec; - url?: string; - isSpecFile?: boolean; + /** + * When spec not provided, load the spec from docusaurus config + * fallback to first configuration if not provided + */ + id?: string; + /** + * docusaurus theme to use + */ themeId?: string; } +interface SpecResult { + hasLogo: boolean; + spec: import('redoc/typings/types').OpenAPISpec; + store: import('redoc/typings').AppStore; + options: import('redoc/typings').RedocRawOptions; + darkThemeOptions: import('redoc/typings').RedocRawOptions; + lightThemeOptions: import('redoc/typings').RedocRawOptions; +} + +type RedocProps = SpecProps & { + className?: string; + optionsOverrides?: import('redoc/typings').RedocRawOptions; + /** + * External URL to load spec file from + */ + url?: string; +}; + +type ApiSchemaProps = Omit< + import('redoc/typings').ObjectDescriptionProps, + 'parser' | 'options' | 'schemaRef' +> & + SpecProps & { + /** + * Ref to the schema + */ + pointer: import('redoc/typings').ObjectDescriptionProps['schemaRef']; + optionsOverrides?: import('redoc/typings').RedocRawOptions; + }; declare module '@theme/Redoc' { - import type { RedocRawOptions } from 'redoc'; - const Redoc: ( - props: SpecProps & { - className?: string; - optionsOverrides?: RedocRawOptions; - }, - ) => JSX.Element; + const Redoc: (props: RedocProps) => JSX.Element; export default Redoc; } @@ -49,31 +80,56 @@ declare module '@theme/ApiDocMdx' { } declare module '@theme/ApiSchema' { - interface ApiSchemaProps { - /** - * If you have multiple apis, then add a `id` field in the specs array - * And pass the same here - */ - id?: string; - /** - * Show the example or not - */ - example?: boolean; - - /** - * Ref to the schema - */ - pointer: string; - } - const ApiSchema: (props: ApiSchemaProps) => JSX.Element; export default ApiSchema; } declare module '@theme/useSpecData' { + type SpecDataResult = Omit & { + /** + * Public path to the spec file used, used by Redoc as download url + */ + url?: string; + }; + /** * Load redocusaurus plugin data by ID */ - const useSpecData: (id?: string) => SpecProps; + const useSpecData: ( + id?: string, + spec?: import('redoc/typings/types').OpenAPISpec, + themeId?: string, + ) => SpecDataResult; export default useSpecData; } + +declare module '@theme/useSpec' { + import { RedocRawOptions } from 'redoc'; + /** + * Load redocusaurus plugin data by ID + */ + const useSpec: ( + specInfo: SpecProps, + optionsOverrides?: RedocRawOptions, + ) => SpecResult; + + export default useSpec; +} + +declare module '@theme/useSpecOptions' { + import { RedocRawOptions } from 'redoc'; + interface SpecOptionsResultProps { + options: RedocRawOptions; + darkThemeOptions: RedocRawOptions; + lightThemeOptions: RedocRawOptions; + } + /** + * Load redocusaurus plugin data by ID + */ + const useSpec: ( + themeId: SpecProps['themeId'], + optionsOverrides?: RedocRawOptions, + ) => SpecOptionsResultProps; + + export default useSpec; +} diff --git a/website/docs/getting-started/Installation.md b/website/docs/getting-started/Installation.md index d23d7e57..85ae546e 100644 --- a/website/docs/getting-started/Installation.md +++ b/website/docs/getting-started/Installation.md @@ -72,7 +72,7 @@ author_url: https://rohit.page ``` The API Doc will be available at the path specific by `route`. To skip adding a route altogether just don't set the `route` property. -You will still be able to reference schema elements manually using [Schema Imports](/docs/guides/schema-imports) or create Custom React Pages using the data and theme components. +You will still be able to reference schema elements manually using [Schema Imports](/docs/guides/component-api-schema) or create Custom React Pages using the data and theme components. If you have a [`redocly.yaml`](https://redocly.com/docs/cli/configuration/) it will be loaded automatically. ## Options diff --git a/website/docs/guides/component-api-schema.mdx b/website/docs/guides/component-api-schema.mdx index 10811d03..d6b28325 100644 --- a/website/docs/guides/component-api-schema.mdx +++ b/website/docs/guides/component-api-schema.mdx @@ -4,7 +4,7 @@ sidebar_position: 2 --- import ApiSchema from '@theme/ApiSchema'; -import openApi from '../../openapi/single-json-file/api-with-examples.json' +import openApi from '../../openapi/using-single-json.openapi.json' # ApiSchema @@ -32,7 +32,7 @@ import ApiSchema from '@theme/ApiSchema'; | spec | OpenAPI spec | A JSON content spec to use | | themeId | String | redocusaurus theme to use - default to `theme-redoc` | -## Example +## Examples ### Basic example @@ -69,33 +69,21 @@ const config = { [ 'redocusaurus', { + openapi: { + path: 'openapi', + routeBasePath: '/examples', + }, specs: [ - { - id: 'using-single-yaml', - spec: 'openapi/single-file/openapi.yaml', - route: '/examples/using-single-yaml/', - }, { id: 'using-remote-url', spec: 'https://redocly.github.io/redoc/openapi.yaml', route: '/examples/using-remote-url/', }, ], - theme: { - /** - * Highlight color for docs - */ - primaryColor: '#1890ff', - /** - * Options to pass to redoc - * @see https://github.com/redocly/redoc#redoc-options-object - */ - options: { disableSearch: true }, - }, + [...] }, ], ], - title: 'Redocusaurus', }; ``` @@ -117,7 +105,7 @@ import ApiSchema from '@theme/ApiSchema'; ### Webpack loader example You can provide a JSON spec to the component like this. Webpack will load the file directly, -you don't need to use docusaurus configuration. +you don't need to use redocusaurus configuration inside `docusaurus.config.js`. ```tsx import ApiSchema from '@theme/ApiSchema'; diff --git a/website/docs/guides/component-redoc.mdx b/website/docs/guides/component-redoc.mdx index 68264bba..54072ee4 100644 --- a/website/docs/guides/component-redoc.mdx +++ b/website/docs/guides/component-redoc.mdx @@ -4,7 +4,7 @@ sidebar_position: 1 --- import Redoc from '@theme/Redoc'; -import openApi from '../../openapi/single-json-file/api-with-examples.json' +import openApi from '../../openapi/using-single-json.openapi.json' # Redoc @@ -29,7 +29,7 @@ import Redoc from '@theme/Redoc'; | spec | OpenAPI spec | A JSON content spec to use | | url | String | External URL to load spec file from | -## Example +## Examples ### External URL example @@ -44,7 +44,7 @@ import Redoc from '@theme/Redoc'; ### Webpack loader example You can provide a JSON spec to the component like this. Webpack will load the file directly, -you don't need to use docusaurus configuration. +you don't need to use redocusaurus configuration inside `docusaurus.config.js`. ```tsx import Redoc from '@theme/Redoc'; diff --git a/website/openapi/single-json-file/api-with-examples.json b/website/openapi/using-single-json.openapi.json similarity index 100% rename from website/openapi/single-json-file/api-with-examples.json rename to website/openapi/using-single-json.openapi.json