mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-22 08:36:33 +03:00
fix: scroll to section sooner when SSR + simplify item ids
This commit is contained in:
parent
28c487d2f1
commit
d1d80422a4
|
@ -65,7 +65,7 @@ export class TagItem extends React.Component<ContentItemProps> {
|
||||||
<Row>
|
<Row>
|
||||||
<MiddlePanel key="middle">
|
<MiddlePanel key="middle">
|
||||||
<H1>
|
<H1>
|
||||||
<ShareLink href={'#' + this.props.item.getHash()} />
|
<ShareLink href={'#' + this.props.item.id} />
|
||||||
{name}
|
{name}
|
||||||
</H1>
|
</H1>
|
||||||
{description !== undefined && <Markdown source={description} />}
|
{description !== undefined && <Markdown source={description} />}
|
||||||
|
|
|
@ -51,7 +51,7 @@ export class Operation extends React.Component<OperationProps> {
|
||||||
<OperationRow>
|
<OperationRow>
|
||||||
<MiddlePanel>
|
<MiddlePanel>
|
||||||
<H2>
|
<H2>
|
||||||
<ShareLink href={'#' + operation.getHash()} />
|
<ShareLink href={'#' + operation.id} />
|
||||||
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
|
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
|
||||||
</H2>
|
</H2>
|
||||||
{options.pathInMiddlePanel && <Endpoint operation={operation} inverted={true} />}
|
{options.pathInMiddlePanel && <Endpoint operation={operation} inverted={true} />}
|
||||||
|
|
|
@ -40,7 +40,7 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
||||||
render() {
|
render() {
|
||||||
const { item, withoutChildren } = this.props;
|
const { item, withoutChildren } = this.props;
|
||||||
return (
|
return (
|
||||||
<MenuItemLi onClick={this.activate} depth={item.depth} innerRef={this.saveRef}>
|
<MenuItemLi onClick={this.activate} depth={item.depth} innerRef={this.saveRef} data-item-id={item.id}>
|
||||||
{item.type === 'operation' ? (
|
{item.type === 'operation' ? (
|
||||||
<OperationMenuItemContent item={item as OperationModel} />
|
<OperationMenuItemContent item={item as OperationModel} />
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { observe } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPISpec } from '../types';
|
import { OpenAPISpec } from '../types';
|
||||||
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
|
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
|
||||||
|
import { HistoryService } from './HistoryService';
|
||||||
import { MarkerService } from './MarkerService';
|
import { MarkerService } from './MarkerService';
|
||||||
import { MenuStore } from './MenuStore';
|
import { MenuStore } from './MenuStore';
|
||||||
import { SpecStore } from './models';
|
import { SpecStore } from './models';
|
||||||
|
@ -63,6 +64,10 @@ export class AppStore {
|
||||||
this.rawOptions = options;
|
this.rawOptions = options;
|
||||||
this.options = new RedocNormalizedOptions(options);
|
this.options = new RedocNormalizedOptions(options);
|
||||||
this.scroll = new ScrollService(this.options);
|
this.scroll = new ScrollService(this.options);
|
||||||
|
|
||||||
|
// update position statically based on hash (in case of SSR)
|
||||||
|
MenuStore.updateOnHash(HistoryService.hash, this.scroll);
|
||||||
|
|
||||||
this.spec = new SpecStore(spec, specUrl, this.options);
|
this.spec = new SpecStore(spec, specUrl, this.options);
|
||||||
this.menu = new MenuStore(this.spec, this.scroll);
|
this.menu = new MenuStore(this.spec, this.scroll);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { GroupModel, OperationModel, SpecStore } from './models';
|
||||||
import { HistoryService } from './HistoryService';
|
import { HistoryService } from './HistoryService';
|
||||||
import { ScrollService } from './ScrollService';
|
import { ScrollService } from './ScrollService';
|
||||||
|
|
||||||
import { flattenByProp } from '../utils';
|
import { flattenByProp, normalizeHash } from '../utils';
|
||||||
import { GROUP_DEPTH } from './MenuBuilder';
|
import { GROUP_DEPTH } from './MenuBuilder';
|
||||||
|
|
||||||
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
||||||
|
@ -24,7 +24,6 @@ export interface IMenuItem {
|
||||||
deprecated?: boolean;
|
deprecated?: boolean;
|
||||||
type: MenuItemType;
|
type: MenuItemType;
|
||||||
|
|
||||||
getHash(): string;
|
|
||||||
deactivate(): void;
|
deactivate(): void;
|
||||||
activate(): void;
|
activate(): void;
|
||||||
}
|
}
|
||||||
|
@ -35,6 +34,17 @@ export const SECTION_ATTR = 'data-section-id';
|
||||||
* Stores all side-menu related information
|
* Stores all side-menu related information
|
||||||
*/
|
*/
|
||||||
export class MenuStore {
|
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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${normalizeHash(hash)}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* active item absolute index (when flattened). -1 means nothing is selected
|
* active item absolute index (when flattened). -1 means nothing is selected
|
||||||
*/
|
*/
|
||||||
|
@ -127,32 +137,13 @@ export class MenuStore {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let item: IMenuItem | undefined;
|
let item: IMenuItem | undefined;
|
||||||
hash = hash.substr(1);
|
hash = normalizeHash(hash);
|
||||||
const namespace = hash.split('/')[0];
|
|
||||||
let ptr = decodeURIComponent(hash.substr(namespace.length + 1));
|
|
||||||
if (namespace === 'section' || namespace === 'tag') {
|
|
||||||
const sectionId = ptr.split('/')[0];
|
|
||||||
ptr = ptr.substr(sectionId.length);
|
|
||||||
|
|
||||||
let searchId;
|
item = this.flatItems.find(i => i.id === hash);
|
||||||
if (namespace === 'section') {
|
|
||||||
searchId = hash;
|
|
||||||
} else {
|
|
||||||
searchId = ptr || namespace + '/' + sectionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
item = this.flatItems.find(i => i.id === searchId);
|
|
||||||
if (item === undefined) {
|
|
||||||
this._scrollService.scrollIntoViewBySelector(`[${SECTION_ATTR}="${searchId}"]`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (namespace === 'operation') {
|
|
||||||
item = this.flatItems.find(i => {
|
|
||||||
return (i as OperationModel).operationId === ptr;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (item) {
|
if (item) {
|
||||||
this.activateAndScroll(item, false);
|
this.activateAndScroll(item, false);
|
||||||
|
} else {
|
||||||
|
this._scrollService.scrollIntoViewBySelector(`[${SECTION_ATTR}="${hash}"]`);
|
||||||
}
|
}
|
||||||
return item !== undefined;
|
return item !== undefined;
|
||||||
}
|
}
|
||||||
|
@ -216,7 +207,7 @@ export class MenuStore {
|
||||||
|
|
||||||
this.activeItemIdx = item.absoluteIdx!;
|
this.activeItemIdx = item.absoluteIdx!;
|
||||||
if (updateHash) {
|
if (updateHash) {
|
||||||
HistoryService.update(item.getHash(), rewriteHistory);
|
HistoryService.update(item.id, rewriteHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (item !== undefined) {
|
while (item !== undefined) {
|
||||||
|
|
|
@ -52,8 +52,4 @@ export class GroupModel implements IMenuItem {
|
||||||
}
|
}
|
||||||
this.active = false;
|
this.active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHash() {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,11 @@ export class OperationModel implements IMenuItem {
|
||||||
parent: GroupModel | undefined,
|
parent: GroupModel | undefined,
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
) {
|
) {
|
||||||
this.id = operationSpec._$ref;
|
this.id =
|
||||||
|
operationSpec.operationId !== undefined
|
||||||
|
? 'operation/' + operationSpec.operationId
|
||||||
|
: this.parent !== undefined ? this.parent.id + operationSpec._$ref : operationSpec._$ref;
|
||||||
|
|
||||||
this.name = getOperationSummary(operationSpec);
|
this.name = getOperationSummary(operationSpec);
|
||||||
this.description = operationSpec.description;
|
this.description = operationSpec.description;
|
||||||
|
|
||||||
|
@ -130,12 +134,6 @@ export class OperationModel implements IMenuItem {
|
||||||
deactivate() {
|
deactivate() {
|
||||||
this.active = false;
|
this.active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHash() {
|
|
||||||
return this.operationId !== undefined
|
|
||||||
? 'operation/' + this.operationId
|
|
||||||
: this.parent !== undefined ? this.parent.id + this.id : this.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNumeric(n) {
|
function isNumeric(n) {
|
||||||
|
|
|
@ -76,9 +76,11 @@ export function hydrate(
|
||||||
const store = AppStore.fromJS(state);
|
const store = AppStore.fromJS(state);
|
||||||
debugTimeEnd('Redoc create store');
|
debugTimeEnd('Redoc create store');
|
||||||
|
|
||||||
debugTime('Redoc hydrate');
|
setTimeout(() => {
|
||||||
hydrateComponent(<Redoc store={store} />, element, callback);
|
debugTime('Redoc hydrate');
|
||||||
debugTimeEnd('Redoc hydrate');
|
hydrateComponent(<Redoc store={store} />, element, callback);
|
||||||
|
debugTimeEnd('Redoc hydrate');
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -24,6 +24,10 @@ export function html2Str(html: string): string {
|
||||||
.join(' ');
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeHash(hash: string): string {
|
||||||
|
return hash.startsWith('#') ? hash.substr(1) : hash;
|
||||||
|
}
|
||||||
|
|
||||||
// scrollIntoViewIfNeeded polyfill
|
// scrollIntoViewIfNeeded polyfill
|
||||||
|
|
||||||
if (typeof Element !== 'undefined' && !(Element as any).prototype.scrollIntoViewIfNeeded) {
|
if (typeof Element !== 'undefined' && !(Element as any).prototype.scrollIntoViewIfNeeded) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user