From e34d42d73d246138e6d4a5499e9e2fdf3549fe5a Mon Sep 17 00:00:00 2001 From: Depickere Sven Date: Tue, 25 Apr 2023 15:35:03 +0100 Subject: [PATCH] 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) {