mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-11 11:26:37 +03:00
chore: refactor HistoryService
This commit is contained in:
parent
d3d35189f5
commit
cfddb3afe1
|
@ -2,7 +2,7 @@ import { observe } from 'mobx';
|
|||
|
||||
import { OpenAPISpec } from '../types';
|
||||
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
|
||||
import { HistoryService } from './HistoryService';
|
||||
import { history } from './HistoryService';
|
||||
import { MarkerService } from './MarkerService';
|
||||
import { MenuStore } from './MenuStore';
|
||||
import { SpecStore } from './models';
|
||||
|
@ -71,10 +71,10 @@ export class AppStore {
|
|||
this.scroll = new ScrollService(this.options);
|
||||
|
||||
// update position statically based on hash (in case of SSR)
|
||||
MenuStore.updateOnHash(HistoryService.hash, this.scroll);
|
||||
MenuStore.updateOnHistory(history.currentId, this.scroll);
|
||||
|
||||
this.spec = new SpecStore(spec, specUrl, this.options);
|
||||
this.menu = new MenuStore(this.spec, this.scroll);
|
||||
this.menu = new MenuStore(this.spec, this.scroll, history);
|
||||
|
||||
if (!this.options.disableSearch) {
|
||||
this.search = new SearchStore();
|
||||
|
@ -89,7 +89,7 @@ export class AppStore {
|
|||
}
|
||||
|
||||
onDidMount() {
|
||||
this.menu.updateOnHash();
|
||||
this.menu.updateOnHistory();
|
||||
this.updateMarkOnMenu(this.menu.activeItemIdx);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ function isSameHash(a: string, b: string): boolean {
|
|||
return a === b || '#' + a === b || a === '#' + b;
|
||||
}
|
||||
|
||||
export class IntHistoryService {
|
||||
export class HistoryService {
|
||||
private _emiter;
|
||||
|
||||
constructor() {
|
||||
|
@ -16,8 +16,12 @@ export class IntHistoryService {
|
|||
this.bind();
|
||||
}
|
||||
|
||||
get hash(): string {
|
||||
return IS_BROWSER ? window.location.hash : '';
|
||||
get currentId(): string {
|
||||
return IS_BROWSER ? window.location.hash.substring(1) : '';
|
||||
}
|
||||
|
||||
linkForId(id: string) {
|
||||
return '#' + id;
|
||||
}
|
||||
|
||||
subscribe(cb): () => void {
|
||||
|
@ -26,7 +30,7 @@ export class IntHistoryService {
|
|||
}
|
||||
|
||||
emit = () => {
|
||||
this._emiter.emit(EVENT, this.hash);
|
||||
this._emiter.emit(EVENT, this.currentId);
|
||||
};
|
||||
|
||||
bind() {
|
||||
|
@ -43,26 +47,31 @@ export class IntHistoryService {
|
|||
|
||||
@bind
|
||||
@debounce
|
||||
update(hash: string | null, rewriteHistory: boolean = false) {
|
||||
if (hash == null || isSameHash(hash, this.hash)) {
|
||||
replace(id: string | null, rewriteHistory: boolean = false) {
|
||||
if (!IS_BROWSER) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == null || isSameHash(id, this.currentId)) {
|
||||
return;
|
||||
}
|
||||
if (rewriteHistory) {
|
||||
if (IS_BROWSER) {
|
||||
window.history.replaceState(null, '', window.location.href.split('#')[0] + '#' + hash);
|
||||
}
|
||||
window.history.replaceState(
|
||||
null,
|
||||
'',
|
||||
window.location.href.split('#')[0] + this.linkForId(id),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
if (IS_BROWSER) {
|
||||
window.history.pushState(null, '', window.location.href.split('#')[0] + '#' + hash);
|
||||
}
|
||||
window.history.pushState(null, '', window.location.href.split('#')[0] + this.linkForId(id));
|
||||
}
|
||||
}
|
||||
|
||||
export const HistoryService = new IntHistoryService();
|
||||
export const history = new HistoryService();
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.dispose(() => {
|
||||
HistoryService.dispose();
|
||||
history.dispose();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ import { action, observable } from 'mobx';
|
|||
import { querySelector } from '../utils/dom';
|
||||
import { SpecStore } from './models';
|
||||
|
||||
import { HistoryService } from './HistoryService';
|
||||
import { history as historyInst, HistoryService } from './HistoryService';
|
||||
import { ScrollService } from './ScrollService';
|
||||
|
||||
import { flattenByProp, normalizeHash } from '../utils';
|
||||
import { flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
|
||||
import { GROUP_DEPTH } from './MenuBuilder';
|
||||
|
||||
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
||||
|
@ -42,11 +42,11 @@ export class MenuStore {
|
|||
* Statically try update scroll position
|
||||
* Used before hydrating from server-side rendered html to scroll page faster
|
||||
*/
|
||||
static updateOnHash(hash: string = HistoryService.hash, scroll: ScrollService) {
|
||||
if (!hash) {
|
||||
static updateOnHistory(id: string = historyInst.currentId, scroll: ScrollService) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${normalizeHash(hash)}"]`);
|
||||
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,7 +73,7 @@ export class MenuStore {
|
|||
* @param spec [SpecStore](#SpecStore) which contains page content structure
|
||||
* @param scroll scroll service instance used by this menu
|
||||
*/
|
||||
constructor(spec: SpecStore, public scroll: ScrollService) {
|
||||
constructor(spec: SpecStore, public scroll: ScrollService, public history: HistoryService) {
|
||||
this.items = spec.contentItems;
|
||||
|
||||
this.flatItems = flattenByProp(this.items || [], 'items');
|
||||
|
@ -84,7 +84,7 @@ export class MenuStore {
|
|||
|
||||
subscribe() {
|
||||
this._unsubscribe = this.scroll.subscribe(this.updateOnScroll);
|
||||
this._hashUnsubscribe = HistoryService.subscribe(this.updateOnHash);
|
||||
this._hashUnsubscribe = this.history.subscribe(this.updateOnHistory);
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -132,24 +132,23 @@ export class MenuStore {
|
|||
|
||||
/**
|
||||
* update active items on hash change
|
||||
* @param hash current hash
|
||||
* @param id current hash
|
||||
*/
|
||||
updateOnHash = (hash: string = HistoryService.hash): boolean => {
|
||||
if (!hash) {
|
||||
return false;
|
||||
updateOnHistory = (id: string = this.history.currentId) => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
let item: IMenuItem | undefined;
|
||||
hash = normalizeHash(hash);
|
||||
|
||||
item = this.flatItems.find(i => i.id === hash);
|
||||
item = this.flatItems.find(i => i.id === id);
|
||||
if (item) {
|
||||
this.activateAndScroll(item, false);
|
||||
} else {
|
||||
if (hash.startsWith(SECURITY_SCHEMES_SECTION_PREFIX)) {
|
||||
if (id.startsWith(SECURITY_SCHEMES_SECTION_PREFIX)) {
|
||||
item = this.flatItems.find(i => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id));
|
||||
this.activate(item);
|
||||
}
|
||||
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${hash}"]`);
|
||||
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -176,13 +175,13 @@ export class MenuStore {
|
|||
/**
|
||||
* activate menu item
|
||||
* @param item item to activate
|
||||
* @param updateHash [true] whether to update location hash
|
||||
* @param updateLocation [true] whether to update location
|
||||
* @param rewriteHistory [false] whether to rewrite browser history (do not create new enrty)
|
||||
*/
|
||||
@action
|
||||
activate(
|
||||
item: IMenuItem | undefined,
|
||||
updateHash: boolean = true,
|
||||
updateLocation: boolean = true,
|
||||
rewriteHistory: boolean = false,
|
||||
) {
|
||||
if ((this.activeItem && this.activeItem.id) === (item && item.id)) {
|
||||
|
@ -190,7 +189,7 @@ export class MenuStore {
|
|||
}
|
||||
this.deactivate(this.activeItem);
|
||||
if (!item) {
|
||||
HistoryService.update('', rewriteHistory);
|
||||
this.history.replace('', rewriteHistory);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -201,8 +200,8 @@ export class MenuStore {
|
|||
}
|
||||
|
||||
this.activeItemIdx = item.absoluteIdx!;
|
||||
if (updateHash) {
|
||||
HistoryService.update(item.id, rewriteHistory);
|
||||
if (updateLocation) {
|
||||
this.history.replace(item.id, rewriteHistory);
|
||||
}
|
||||
|
||||
item.activate();
|
||||
|
@ -229,10 +228,14 @@ export class MenuStore {
|
|||
* @see MenuStore.activate
|
||||
*/
|
||||
@action.bound
|
||||
activateAndScroll(item: IMenuItem | undefined, updateHash?: boolean, rewriteHistory?: boolean) {
|
||||
activateAndScroll(
|
||||
item: IMenuItem | undefined,
|
||||
updateLocation?: boolean,
|
||||
rewriteHistory?: boolean,
|
||||
) {
|
||||
// item here can be a copy from search results so find corresponding item from menu
|
||||
const menuItem = (item && this.getItemById(item.id)) || item;
|
||||
this.activate(menuItem, updateHash, rewriteHistory);
|
||||
this.activate(menuItem, updateLocation, rewriteHistory);
|
||||
this.scrollToActive();
|
||||
if (!menuItem || !menuItem.items.length) {
|
||||
this.closeSidebar();
|
||||
|
|
|
@ -1,25 +1,34 @@
|
|||
import { HistoryService } from '../HistoryService';
|
||||
import { history } from '../HistoryService';
|
||||
|
||||
describe('History service', () => {
|
||||
test('should be an instance', () => {
|
||||
expect(typeof HistoryService).not.toBe('function');
|
||||
expect(HistoryService.subscribe).toBeDefined();
|
||||
expect(typeof history).not.toBe('function');
|
||||
expect(history.subscribe).toBeDefined();
|
||||
});
|
||||
|
||||
test('History subscribe', () => {
|
||||
const fn = jest.fn();
|
||||
HistoryService.subscribe(fn);
|
||||
HistoryService.emit();
|
||||
history.subscribe(fn);
|
||||
history.emit();
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('History subscribe should return unsubsribe function', () => {
|
||||
const fn = jest.fn();
|
||||
const unsubscribe = HistoryService.subscribe(fn);
|
||||
HistoryService.emit();
|
||||
const unsubscribe = history.subscribe(fn);
|
||||
history.emit();
|
||||
expect(fn).toHaveBeenCalled();
|
||||
unsubscribe();
|
||||
HistoryService.emit();
|
||||
history.emit();
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('currentId should return correct id', () => {
|
||||
window.location.hash = '#testid';
|
||||
expect(history.currentId).toEqual('testid');
|
||||
});
|
||||
|
||||
test('should return correct link for id', () => {
|
||||
expect(history.linkForId('testid')).toEqual('#testid');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,10 +24,6 @@ export function html2Str(html: string): string {
|
|||
.join(' ');
|
||||
}
|
||||
|
||||
export function normalizeHash(hash: string): string {
|
||||
return hash.startsWith('#') ? hash.substr(1) : hash;
|
||||
}
|
||||
|
||||
// scrollIntoViewIfNeeded polyfill
|
||||
|
||||
if (typeof Element !== 'undefined' && !(Element as any).prototype.scrollIntoViewIfNeeded) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user