From 8d1dbf42f1088143030d517ebc7068480c5d4cae Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Fri, 21 Apr 2023 10:48:09 +0200 Subject: [PATCH 01/13] feat(): Add and move custom components to custom folder --- .../{Fields => Custom}/ExtraDescriptions.tsx | 0 .../Custom/ExtraOperationInformation.tsx | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+) rename src/components/{Fields => Custom}/ExtraDescriptions.tsx (100%) create mode 100644 src/components/Custom/ExtraOperationInformation.tsx diff --git a/src/components/Fields/ExtraDescriptions.tsx b/src/components/Custom/ExtraDescriptions.tsx similarity index 100% rename from src/components/Fields/ExtraDescriptions.tsx rename to src/components/Custom/ExtraDescriptions.tsx diff --git a/src/components/Custom/ExtraOperationInformation.tsx b/src/components/Custom/ExtraOperationInformation.tsx new file mode 100644 index 00000000..b8325291 --- /dev/null +++ b/src/components/Custom/ExtraOperationInformation.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; + +import { MenuItemTitle, OperationBadge } from '../SideMenu'; +import { observer } from 'mobx-react'; +import { shortenHTTPVerb } from '../../utils'; + +export interface ExtraOperationInformationProps { + httpVerb: string; + label: string; +} + +export const ExtraOperationInformation = observer((props: ExtraOperationInformationProps) => { + const item = props; + + return ( +
+ {shortenHTTPVerb(item.httpVerb)} + {item.label} +
+ ); +}); From 125fb6aa079403c04d9c9ddd42ead9ff297539de Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Fri, 21 Apr 2023 10:48:25 +0200 Subject: [PATCH 02/13] feat(): Add new extra property --- src/types/miles.constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/miles.constants.ts b/src/types/miles.constants.ts index 4d73cdc3..3bfba6c5 100644 --- a/src/types/miles.constants.ts +++ b/src/types/miles.constants.ts @@ -1,4 +1,5 @@ export const MilesConstants = { MILES_EXTRA_DESCRIPTION_PROPERTY_NAME: 'x-miles-extra-description', + MILES_VALIDATION_MODEL_PROPERTY_NAME: 'x-miles-validation-model', HIDE_LOGO_QUERY_PARAM: 'hideLogo', }; From e96e9d85c47999da97119d774f8b3e19bad90951 Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Fri, 21 Apr 2023 14:18:36 +0200 Subject: [PATCH 03/13] feat(): Expose certain extensions --- src/services/models/Schema.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 5bd42245..e5d868b7 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -457,6 +457,14 @@ function buildFields( field[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME] != null ? field[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME] : {}, + [MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME]: + field[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME] != null + ? field[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME] + : {}, + [MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME]: + field[MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME] != null + ? field[MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME] + : {}, }, $ref + '/properties/' + fieldName, options, @@ -494,6 +502,14 @@ function buildFields( field[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME] != null ? field[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME] : {}, + [MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME]: + field[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME] != null + ? field[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME] + : {}, + [MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME]: + field[MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME] != null + ? field[MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME] + : {}, }, `${$ref}/patternProperties/${fieldName}`, options, From aa159377d50e20bd12fe7d978b276cae3570fad9 Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Fri, 21 Apr 2023 14:18:49 +0200 Subject: [PATCH 04/13] feat(): Expose certain extensions --- src/services/models/Operation.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index e26b4a4c..54905212 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -25,6 +25,7 @@ import type { OpenAPIParser } from '../OpenAPIParser'; import type { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import type { MediaContentModel } from './MediaContent'; import type { ContentItemModel, ExtendedOpenAPIOperation, IMenuItem } from '../types'; +import { MilesConstants } from '../../types'; export interface XPayloadSample { lang: 'payload'; @@ -80,6 +81,8 @@ export class OperationModel implements IMenuItem { isWebhook: boolean; isEvent: boolean; + extraDescription: Record; + constructor( private parser: OpenAPIParser, private operationSpec: ExtendedOpenAPIOperation, @@ -135,6 +138,12 @@ export class OperationModel implements IMenuItem { security => new SecurityRequirementModel(security, parser), ); + if (operationSpec[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME] == undefined) { + this.extraDescription = operationSpec.extraDescription; + } else { + this.extraDescription = operationSpec[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME]; + } + this.servers = normalizeServers( parser.specUrl, operationSpec.servers || operationSpec.pathServers || parser.spec.servers || [], From efbcec58a026864d25a19e1d738da8f2c6f5a30b Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Fri, 21 Apr 2023 14:18:54 +0200 Subject: [PATCH 05/13] feat(): Expose certain extensions --- src/services/models/Field.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/services/models/Field.ts b/src/services/models/Field.ts index c502947f..59fcd73f 100644 --- a/src/services/models/Field.ts +++ b/src/services/models/Field.ts @@ -60,6 +60,11 @@ export class FieldModel { serializationMime?: string; extraDescription: Record; + extraApiReferenceForValidation: { + httpVerb: string; + label: string; + description: string; + }; constructor( parser: OpenAPIParser, @@ -104,6 +109,17 @@ export class FieldModel { this.extraDescription = info[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME]; } + if ( + info[MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME] !== undefined && + info[MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME]?.label + ) { + this.extraApiReferenceForValidation = { + label: info[MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME]?.label, + httpVerb: info[MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME]?.httpVerb, + description: info[MilesConstants.MILES_VALIDATION_MODEL_PROPERTY_NAME]?.description, + }; + } + if (serializationMime) { this.serializationMime = serializationMime; } else if (info.style) { From 6222fe0e4170f2ba2fadcd6350d954a25a43af04 Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Fri, 21 Apr 2023 14:19:08 +0200 Subject: [PATCH 06/13] feat(): Expose extra endpoint information --- src/components/Fields/FieldDetails.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/Fields/FieldDetails.tsx b/src/components/Fields/FieldDetails.tsx index b7f5afed..68efcbd5 100644 --- a/src/components/Fields/FieldDetails.tsx +++ b/src/components/Fields/FieldDetails.tsx @@ -13,7 +13,7 @@ import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocument import { Markdown } from '../Markdown/Markdown'; import { EnumValues } from './EnumValues'; import { Extensions } from './Extensions'; -import { ExtraDescription } from './ExtraDescriptions'; +import { ExtraDescription } from '../Custom/ExtraDescriptions'; import { FieldProps } from './Field'; import { Examples } from './Examples'; import { ConstraintsView } from './FieldContstraints'; @@ -25,6 +25,7 @@ import { l } from '../../services/Labels'; import { OptionsContext } from '../OptionsProvider'; import { Pattern } from './Pattern'; import { ArrayItemDetails } from './ArrayItemDetails'; +import { ExtraOperationInformation } from '../Custom/ExtraOperationInformation'; export const FieldDetailsComponent = observer((props: FieldProps) => { const { enumSkipQuotes, hideSchemaTitles } = React.useContext(OptionsContext); @@ -38,6 +39,7 @@ export const FieldDetailsComponent = observer((props: FieldProps) => { in: _in, const: _const, extraDescription, + extraApiReferenceForValidation, } = field; const isArrayType = schema.type === 'array'; @@ -109,6 +111,13 @@ export const FieldDetailsComponent = observer((props: FieldProps) => { {!renderDiscriminatorSwitch && ( )}{' '} + {!!extraApiReferenceForValidation && ( + + )} {extraDescription && } {renderedExamples} From 9cc2e672e3936a55e31d484180c6c51c9d79a947 Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Fri, 21 Apr 2023 14:19:19 +0200 Subject: [PATCH 07/13] feat(): Add styling --- src/components/Custom/ExtraOperationInformation.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/Custom/ExtraOperationInformation.tsx b/src/components/Custom/ExtraOperationInformation.tsx index b8325291..22e7497f 100644 --- a/src/components/Custom/ExtraOperationInformation.tsx +++ b/src/components/Custom/ExtraOperationInformation.tsx @@ -5,17 +5,19 @@ import { observer } from 'mobx-react'; import { shortenHTTPVerb } from '../../utils'; export interface ExtraOperationInformationProps { + description: string; httpVerb: string; label: string; } export const ExtraOperationInformation = observer((props: ExtraOperationInformationProps) => { - const item = props; - return ( -
- {shortenHTTPVerb(item.httpVerb)} - {item.label} +
+
{props.description}
+
+ {shortenHTTPVerb(props.httpVerb)} + {props.label} +
); }); From 1a9cb581bf744148a00c02ff73a9d4b05747c79f Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Mon, 24 Apr 2023 14:24:42 +0100 Subject: [PATCH 08/13] revert(): Revert custom properties from operation --- src/services/models/Operation.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index 54905212..e26b4a4c 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -25,7 +25,6 @@ import type { OpenAPIParser } from '../OpenAPIParser'; import type { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import type { MediaContentModel } from './MediaContent'; import type { ContentItemModel, ExtendedOpenAPIOperation, IMenuItem } from '../types'; -import { MilesConstants } from '../../types'; export interface XPayloadSample { lang: 'payload'; @@ -81,8 +80,6 @@ export class OperationModel implements IMenuItem { isWebhook: boolean; isEvent: boolean; - extraDescription: Record; - constructor( private parser: OpenAPIParser, private operationSpec: ExtendedOpenAPIOperation, @@ -138,12 +135,6 @@ export class OperationModel implements IMenuItem { security => new SecurityRequirementModel(security, parser), ); - if (operationSpec[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME] == undefined) { - this.extraDescription = operationSpec.extraDescription; - } else { - this.extraDescription = operationSpec[MilesConstants.MILES_EXTRA_DESCRIPTION_PROPERTY_NAME]; - } - this.servers = normalizeServers( parser.specUrl, operationSpec.servers || operationSpec.pathServers || parser.spec.servers || [], From e34d42d73d246138e6d4a5499e9e2fdf3549fe5a Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Tue, 25 Apr 2023 15:35:03 +0100 Subject: [PATCH 09/13] feat(): Fix navigation with # in markdown --- src/services/AppStore.ts | 4 ++-- src/services/HistoryService.ts | 26 +++++++++++++++++++------- src/services/MenuStore.ts | 29 ++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/services/AppStore.ts b/src/services/AppStore.ts index 77b61380..0f166595 100644 --- a/src/services/AppStore.ts +++ b/src/services/AppStore.ts @@ -74,9 +74,9 @@ export class AppStore { // override the openApi standard to version 3.1.0 // TODO remove when fully supporting open API 3.1.0 - spec.openapi = "3.1.0"; + spec.openapi = '3.1.0'; this.spec = new SpecStore(spec, specUrl, this.options); - this.menu = new MenuStore(this.spec, this.scroll, this.history); + this.menu = new MenuStore(this.spec, this.scroll, this.history, this.options); if (!this.options.disableSearch) { this.search = new SearchStore(); diff --git a/src/services/HistoryService.ts b/src/services/HistoryService.ts index d2c4b1e6..2965fb13 100644 --- a/src/services/HistoryService.ts +++ b/src/services/HistoryService.ts @@ -18,7 +18,13 @@ export class HistoryService { get currentId(): string { if (IS_BROWSER) { if (this.shouldQueryParamNavigationBeUsed()) { - return this.getQueryParams(window.location.search); + // When the window.location.hash is not empty this means that we have clicked on + // router that's for example stored in the description via markdown + if (window.location.hash == '') { + return this.getQueryParams(window.location.search); + } else { + return decodeURIComponent(window.location.hash.substring(1)); + } } else { return decodeURIComponent(window.location.hash.substring(1)); } @@ -65,7 +71,13 @@ export class HistoryService { return; } - if (id == null || id === this.currentId) { + // If there currentId and the ID are equal but there is still + // a hash left when using query param navigation + // that means that the URL hasn't been overridden + if ( + id == null || + (id === this.currentId && this.checkIfThereIsHashLeftWhenQueryParamNavigationShouldBeUsed()) + ) { return; } if (rewriteHistory) { @@ -103,6 +115,8 @@ export class HistoryService { private getFullUrl(id: string): string { const url = this.getUrl(); + // Override the hash so it's removed when using query param navigation + url.hash = ''; url.searchParams.set('redoc', id); return url.toString(); } @@ -111,9 +125,7 @@ export class HistoryService { return new URL(window.location.href); } - // private getQueryParamKey(): void { - // let searchParams = new URLSearchParams(window.location.search); - // searchParams.get('redoc') - // - // } + private checkIfThereIsHashLeftWhenQueryParamNavigationShouldBeUsed(): boolean { + return !(this.shouldQueryParamNavigationBeUsed() && window.location.hash != ''); + } } diff --git a/src/services/MenuStore.ts b/src/services/MenuStore.ts index 44b1d0cb..41660e6f 100644 --- a/src/services/MenuStore.ts +++ b/src/services/MenuStore.ts @@ -8,6 +8,7 @@ import { GROUP_DEPTH } from './MenuBuilder'; import type { SpecStore } from './models'; import type { ScrollService } from './ScrollService'; import type { IMenuItem } from './types'; +import { RedocNormalizedOptions } from './RedocNormalizedOptions'; /** Generic interface for MenuItems */ @@ -42,6 +43,7 @@ export class MenuStore { items: IMenuItem[]; flatItems: IMenuItem[]; + private options: RedocNormalizedOptions; /** * cached flattened menu items to support absolute indexing @@ -53,11 +55,19 @@ export class MenuStore { * * @param spec [SpecStore](#SpecStore) which contains page content structure * @param scroll scroll service instance used by this menu + * @param history the history service + * @param options the RedocNormalizedOptions that can be used to retrieve the config options */ - constructor(spec: SpecStore, public scroll: ScrollService, public history: HistoryService) { + constructor( + spec: SpecStore, + public scroll: ScrollService, + public history: HistoryService, + options: RedocNormalizedOptions, + ) { makeObservable(this); this.items = spec.contentItems; + this.options = options; this.flatItems = flattenByProp(this.items || [], 'items'); this.flatItems.forEach((item, idx) => (item.absoluteIdx = idx)); @@ -126,16 +136,28 @@ export class MenuStore { item = this.flatItems.find(i => i.id === id); if (item) { - this.activateAndScroll(item, false); + this.activateAndScrollWithNavigationStrategy(item); } else { if (id.startsWith(SECURITY_SCHEMES_SECTION_PREFIX)) { item = this.flatItems.find(i => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id)); - this.activateAndScroll(item, false); + this.activateAndScrollWithNavigationStrategy(item); } this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(id)}"]`); } }; + private activateAndScrollWithNavigationStrategy(item: IMenuItem | undefined): void { + if (this.shouldQueryParamNavigationBeUsed()) { + this.activateAndScroll(item, true, true); + } else { + this.activateAndScroll(item, false); + } + } + + private shouldQueryParamNavigationBeUsed(): boolean { + return this.options?.userQueryParamToNavigate; + } + /** * get section/operation DOM Node related to the item or null if it doesn't exist * @param idx item absolute index @@ -237,6 +259,7 @@ export class MenuStore { ) { // item here can be a copy from search results so find corresponding item from menu const menuItem = (item && this.getItemById(item.id)) || item; + console.log('activateAndScroll', menuItem?.id, updateLocation, rewriteHistory); this.activate(menuItem, updateLocation, rewriteHistory); this.scrollToActive(); if (!menuItem || !menuItem.items.length) { From 8162672f516af4646f72cf8fca4fd090b5a5915f Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Tue, 25 Apr 2023 16:07:30 +0100 Subject: [PATCH 10/13] :package: bump to version 2.0.0-0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a030a30..f7958bd7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redoc", - "version": "2.0.0-0.2.0", + "version": "2.0.0-0.3.0", "description": "ReDoc", "repository": { "type": "git", From e77fedf5db617b1a66af77d601362883f64bcbdb Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Mon, 8 May 2023 12:44:48 +0200 Subject: [PATCH 11/13] feat(): Change order of the extraApiReferenceForValidation --- src/components/Fields/FieldDetails.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/Fields/FieldDetails.tsx b/src/components/Fields/FieldDetails.tsx index 68efcbd5..de37063b 100644 --- a/src/components/Fields/FieldDetails.tsx +++ b/src/components/Fields/FieldDetails.tsx @@ -111,13 +111,6 @@ export const FieldDetailsComponent = observer((props: FieldProps) => { {!renderDiscriminatorSwitch && ( )}{' '} - {!!extraApiReferenceForValidation && ( - - )} {extraDescription && } {renderedExamples} @@ -129,6 +122,13 @@ export const FieldDetailsComponent = observer((props: FieldProps) => { )} {(renderDiscriminatorSwitch && renderDiscriminatorSwitch(props)) || null} {(_const && ) || null} + {!!extraApiReferenceForValidation && ( + + )}
); }); From fb50e2b4273d70538f2917739b9d26cb37ce1a0d Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Wed, 7 Jun 2023 11:52:27 +0200 Subject: [PATCH 12/13] ref: clean up --- src/services/MenuStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/MenuStore.ts b/src/services/MenuStore.ts index 41660e6f..4ef2860c 100644 --- a/src/services/MenuStore.ts +++ b/src/services/MenuStore.ts @@ -259,7 +259,6 @@ export class MenuStore { ) { // item here can be a copy from search results so find corresponding item from menu const menuItem = (item && this.getItemById(item.id)) || item; - console.log('activateAndScroll', menuItem?.id, updateLocation, rewriteHistory); this.activate(menuItem, updateLocation, rewriteHistory); this.scrollToActive(); if (!menuItem || !menuItem.items.length) { From 700444d6909a27e87c1b4369644b90a730d1c67b Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Wed, 7 Jun 2023 12:01:10 +0200 Subject: [PATCH 13/13] =?UTF-8?q?=F0=9F=93=A6=20bump=20to=20version=202.0.?= =?UTF-8?q?0-0.4.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f7958bd7..2b72d352 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redoc", - "version": "2.0.0-0.3.0", + "version": "2.0.0-0.4.0", "description": "ReDoc", "repository": { "type": "git",