Skip to content

Commit d1d8042

Browse files
committed
fix: scroll to section sooner when SSR + simplify item ids
1 parent 28c487d commit d1d8042

File tree

9 files changed

+39
-43
lines changed

9 files changed

+39
-43
lines changed

src/components/ContentItems/ContentItems.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class TagItem extends React.Component<ContentItemProps> {
6565
<Row>
6666
<MiddlePanel key="middle">
6767
<H1>
68-
<ShareLink href={'#' + this.props.item.getHash()} />
68+
<ShareLink href={'#' + this.props.item.id} />
6969
{name}
7070
</H1>
7171
{description !== undefined && <Markdown source={description} />}

src/components/Operation/Operation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class Operation extends React.Component<OperationProps> {
5151
<OperationRow>
5252
<MiddlePanel>
5353
<H2>
54-
<ShareLink href={'#' + operation.getHash()} />
54+
<ShareLink href={'#' + operation.id} />
5555
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
5656
</H2>
5757
{options.pathInMiddlePanel && <Endpoint operation={operation} inverted={true} />}

src/components/SideMenu/MenuItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class MenuItem extends React.Component<MenuItemProps> {
4040
render() {
4141
const { item, withoutChildren } = this.props;
4242
return (
43-
<MenuItemLi onClick={this.activate} depth={item.depth} innerRef={this.saveRef}>
43+
<MenuItemLi onClick={this.activate} depth={item.depth} innerRef={this.saveRef} data-item-id={item.id}>
4444
{item.type === 'operation' ? (
4545
<OperationMenuItemContent item={item as OperationModel} />
4646
) : (

src/services/AppStore.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { observe } from 'mobx';
22

33
import { OpenAPISpec } from '../types';
44
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
5+
import { HistoryService } from './HistoryService';
56
import { MarkerService } from './MarkerService';
67
import { MenuStore } from './MenuStore';
78
import { SpecStore } from './models';
@@ -63,6 +64,10 @@ export class AppStore {
6364
this.rawOptions = options;
6465
this.options = new RedocNormalizedOptions(options);
6566
this.scroll = new ScrollService(this.options);
67+
68+
// update position statically based on hash (in case of SSR)
69+
MenuStore.updateOnHash(HistoryService.hash, this.scroll);
70+
6671
this.spec = new SpecStore(spec, specUrl, this.options);
6772
this.menu = new MenuStore(this.spec, this.scroll);
6873

src/services/MenuStore.ts

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { GroupModel, OperationModel, SpecStore } from './models';
55
import { HistoryService } from './HistoryService';
66
import { ScrollService } from './ScrollService';
77

8-
import { flattenByProp } from '../utils';
8+
import { flattenByProp, normalizeHash } from '../utils';
99
import { GROUP_DEPTH } from './MenuBuilder';
1010

1111
export type MenuItemGroupType = 'group' | 'tag' | 'section';
@@ -24,7 +24,6 @@ export interface IMenuItem {
2424
deprecated?: boolean;
2525
type: MenuItemType;
2626

27-
getHash(): string;
2827
deactivate(): void;
2928
activate(): void;
3029
}
@@ -35,6 +34,17 @@ export const SECTION_ATTR = 'data-section-id';
3534
* Stores all side-menu related information
3635
*/
3736
export class MenuStore {
37+
/**
38+
* Statically try update scroll position
39+
* Used before hydrating from server-side rendered html to scroll page faster
40+
*/
41+
static updateOnHash(hash: string = HistoryService.hash, scroll: ScrollService) {
42+
if (!hash) {
43+
return;
44+
}
45+
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${normalizeHash(hash)}"]`);
46+
}
47+
3848
/**
3949
* active item absolute index (when flattened). -1 means nothing is selected
4050
*/
@@ -127,32 +137,13 @@ export class MenuStore {
127137
return false;
128138
}
129139
let item: IMenuItem | undefined;
130-
hash = hash.substr(1);
131-
const namespace = hash.split('/')[0];
132-
let ptr = decodeURIComponent(hash.substr(namespace.length + 1));
133-
if (namespace === 'section' || namespace === 'tag') {
134-
const sectionId = ptr.split('/')[0];
135-
ptr = ptr.substr(sectionId.length);
136-
137-
let searchId;
138-
if (namespace === 'section') {
139-
searchId = hash;
140-
} else {
141-
searchId = ptr || namespace + '/' + sectionId;
142-
}
140+
hash = normalizeHash(hash);
143141

144-
item = this.flatItems.find(i => i.id === searchId);
145-
if (item === undefined) {
146-
this._scrollService.scrollIntoViewBySelector(`[${SECTION_ATTR}="${searchId}"]`);
147-
return false;
148-
}
149-
} else if (namespace === 'operation') {
150-
item = this.flatItems.find(i => {
151-
return (i as OperationModel).operationId === ptr;
152-
});
153-
}
142+
item = this.flatItems.find(i => i.id === hash);
154143
if (item) {
155144
this.activateAndScroll(item, false);
145+
} else {
146+
this._scrollService.scrollIntoViewBySelector(`[${SECTION_ATTR}="${hash}"]`);
156147
}
157148
return item !== undefined;
158149
}
@@ -216,7 +207,7 @@ export class MenuStore {
216207

217208
this.activeItemIdx = item.absoluteIdx!;
218209
if (updateHash) {
219-
HistoryService.update(item.getHash(), rewriteHistory);
210+
HistoryService.update(item.id, rewriteHistory);
220211
}
221212

222213
while (item !== undefined) {

src/services/models/Group.model.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,4 @@ export class GroupModel implements IMenuItem {
5252
}
5353
this.active = false;
5454
}
55-
56-
getHash() {
57-
return this.id;
58-
}
5955
}

src/services/models/Operation.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ export class OperationModel implements IMenuItem {
6262
parent: GroupModel | undefined,
6363
options: RedocNormalizedOptions,
6464
) {
65-
this.id = operationSpec._$ref;
65+
this.id =
66+
operationSpec.operationId !== undefined
67+
? 'operation/' + operationSpec.operationId
68+
: this.parent !== undefined ? this.parent.id + operationSpec._$ref : operationSpec._$ref;
69+
6670
this.name = getOperationSummary(operationSpec);
6771
this.description = operationSpec.description;
6872

@@ -130,12 +134,6 @@ export class OperationModel implements IMenuItem {
130134
deactivate() {
131135
this.active = false;
132136
}
133-
134-
getHash() {
135-
return this.operationId !== undefined
136-
? 'operation/' + this.operationId
137-
: this.parent !== undefined ? this.parent.id + this.id : this.id;
138-
}
139137
}
140138

141139
function isNumeric(n) {

src/standalone.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,11 @@ export function hydrate(
7676
const store = AppStore.fromJS(state);
7777
debugTimeEnd('Redoc create store');
7878

79-
debugTime('Redoc hydrate');
80-
hydrateComponent(<Redoc store={store} />, element, callback);
81-
debugTimeEnd('Redoc hydrate');
79+
setTimeout(() => {
80+
debugTime('Redoc hydrate');
81+
hydrateComponent(<Redoc store={store} />, element, callback);
82+
debugTimeEnd('Redoc hydrate');
83+
}, 0);
8284
}
8385

8486
/**

src/utils/dom.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export function html2Str(html: string): string {
2424
.join(' ');
2525
}
2626

27+
export function normalizeHash(hash: string): string {
28+
return hash.startsWith('#') ? hash.substr(1) : hash;
29+
}
30+
2731
// scrollIntoViewIfNeeded polyfill
2832

2933
if (typeof Element !== 'undefined' && !(Element as any).prototype.scrollIntoViewIfNeeded) {

0 commit comments

Comments
 (0)