feat(core): remember the scroll position of doc when routing forward and backward (#8631)
close AF-1011 https://github.com/user-attachments/assets/d2dfeee2-926f-4760-b3fb-8baf5ff90aa9
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { notify, Scrollable, useHasScrollTop } from '@affine/component';
|
import { notify, Scrollable } from '@affine/component';
|
||||||
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
|
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
|
||||||
import type { ChatPanel } from '@affine/core/blocksuite/presets/ai';
|
import type { ChatPanel } from '@affine/core/blocksuite/presets/ai';
|
||||||
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
||||||
@@ -33,7 +33,7 @@ import {
|
|||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { memo, useCallback, useEffect, useRef } from 'react';
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { AffineErrorBoundary } from '../../../../components/affine/affine-error-boundary';
|
import { AffineErrorBoundary } from '../../../../components/affine/affine-error-boundary';
|
||||||
@@ -227,28 +227,36 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
editor.setEditorContainer(editorContainer);
|
|
||||||
const unbind = editor.bindEditorContainer(
|
const unbind = editor.bindEditorContainer(
|
||||||
editorContainer,
|
editorContainer,
|
||||||
(editorContainer as any).docTitle // set from proxy
|
(editorContainer as any).docTitle, // set from proxy
|
||||||
|
scrollViewportRef.current
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unbind();
|
unbind();
|
||||||
editor.setEditorContainer(null);
|
|
||||||
disposable.dispose();
|
disposable.dispose();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[editor, openPage, docCollection.id, jumpToPageBlock, t]
|
[editor, openPage, docCollection.id, jumpToPageBlock, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [refCallback, hasScrollTop] = useHasScrollTop();
|
const [hasScrollTop, setHasScrollTop] = useState(false);
|
||||||
|
|
||||||
const openOutlinePanel = useCallback(() => {
|
const openOutlinePanel = useCallback(() => {
|
||||||
workbench.openSidebar();
|
workbench.openSidebar();
|
||||||
view.activeSidebarTab('outline');
|
view.activeSidebarTab('outline');
|
||||||
}, [workbench, view]);
|
}, [workbench, view]);
|
||||||
|
|
||||||
|
const scrollViewportRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const handleScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
|
||||||
|
const scrollTop = e.currentTarget.scrollTop;
|
||||||
|
|
||||||
|
const hasScrollTop = scrollTop > 0;
|
||||||
|
setHasScrollTop(hasScrollTop);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FrameworkScope scope={editor.scope}>
|
<FrameworkScope scope={editor.scope}>
|
||||||
<ViewHeader>
|
<ViewHeader>
|
||||||
@@ -265,7 +273,8 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
<TopTip pageId={doc.id} workspace={workspace} />
|
<TopTip pageId={doc.id} workspace={workspace} />
|
||||||
<Scrollable.Root>
|
<Scrollable.Root>
|
||||||
<Scrollable.Viewport
|
<Scrollable.Viewport
|
||||||
ref={refCallback}
|
onScroll={handleScroll}
|
||||||
|
ref={scrollViewportRef}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'affine-page-viewport',
|
'affine-page-viewport',
|
||||||
styles.affineDocViewport,
|
styles.affineDocViewport,
|
||||||
|
|||||||
@@ -234,11 +234,7 @@ const SharePageInner = ({
|
|||||||
if (!editor) {
|
if (!editor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editor.setEditorContainer(editorContainer);
|
const unbind = editor.bindEditorContainer(editorContainer);
|
||||||
const unbind = editor.bindEditorContainer(
|
|
||||||
editorContainer,
|
|
||||||
(editorContainer as any).docTitle
|
|
||||||
);
|
|
||||||
|
|
||||||
const disposable = new DisposableGroup();
|
const disposable = new DisposableGroup();
|
||||||
const refNodeSlots =
|
const refNodeSlots =
|
||||||
@@ -263,7 +259,6 @@ const SharePageInner = ({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unbind();
|
unbind();
|
||||||
editor.setEditorContainer(null);
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[editor, setActiveBlocksuiteEditor, jumpToPageBlock, openPage, workspaceId]
|
[editor, setActiveBlocksuiteEditor, jumpToPageBlock, openPage, workspaceId]
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import {
|
|||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { PageHeader } from '../../../components';
|
import { PageHeader } from '../../../components';
|
||||||
@@ -67,6 +67,8 @@ const DetailPageImpl = () => {
|
|||||||
|
|
||||||
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
||||||
const { openPage, jumpToPageBlock } = useNavigateHelper();
|
const { openPage, jumpToPageBlock } = useNavigateHelper();
|
||||||
|
const scrollViewportRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const editorContainer = useLiveData(editor.editorContainer$);
|
const editorContainer = useLiveData(editor.editorContainer$);
|
||||||
|
|
||||||
const enableKeyboardToolbar =
|
const enableKeyboardToolbar =
|
||||||
@@ -157,7 +159,11 @@ const DetailPageImpl = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.setEditorContainer(editorContainer);
|
editor.bindEditorContainer(
|
||||||
|
editorContainer,
|
||||||
|
null,
|
||||||
|
scrollViewportRef.current
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disposable.dispose();
|
disposable.dispose();
|
||||||
@@ -171,6 +177,7 @@ const DetailPageImpl = () => {
|
|||||||
<div className={styles.mainContainer}>
|
<div className={styles.mainContainer}>
|
||||||
<div
|
<div
|
||||||
data-mode={mode}
|
data-mode={mode}
|
||||||
|
ref={scrollViewportRef}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'affine-page-viewport',
|
'affine-page-viewport',
|
||||||
styles.affineDocViewport,
|
styles.affineDocViewport,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { DefaultOpenProperty } from '@affine/core/components/doc-properties';
|
import type { DefaultOpenProperty } from '@affine/core/components/doc-properties';
|
||||||
import type {
|
import {
|
||||||
DocMode,
|
type DocMode,
|
||||||
EdgelessRootService,
|
EdgelessRootService,
|
||||||
ReferenceParams,
|
type ReferenceParams,
|
||||||
} from '@blocksuite/affine/blocks';
|
} from '@blocksuite/affine/blocks';
|
||||||
import type {
|
import type {
|
||||||
AffineEditorContainer,
|
AffineEditorContainer,
|
||||||
@@ -13,6 +13,7 @@ import { effect } from '@preact/signals-core';
|
|||||||
import type { DocService, WorkspaceService } from '@toeverything/infra';
|
import type { DocService, WorkspaceService } from '@toeverything/infra';
|
||||||
import { Entity, LiveData } from '@toeverything/infra';
|
import { Entity, LiveData } from '@toeverything/infra';
|
||||||
import { defaults, isEqual, omit } from 'lodash-es';
|
import { defaults, isEqual, omit } from 'lodash-es';
|
||||||
|
import { skip } from 'rxjs';
|
||||||
|
|
||||||
import { paramsParseOptions, preprocessParams } from '../../navigation/utils';
|
import { paramsParseOptions, preprocessParams } from '../../navigation/utils';
|
||||||
import type { WorkbenchView } from '../../workbench';
|
import type { WorkbenchView } from '../../workbench';
|
||||||
@@ -34,6 +35,34 @@ export class Editor extends Entity {
|
|||||||
readonly defaultOpenProperty$ = new LiveData<DefaultOpenProperty | undefined>(
|
readonly defaultOpenProperty$ = new LiveData<DefaultOpenProperty | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
workbenchView: WorkbenchView | null = null;
|
||||||
|
defaultScrollPosition:
|
||||||
|
| number
|
||||||
|
| {
|
||||||
|
centerX: number;
|
||||||
|
centerY: number;
|
||||||
|
zoom: number;
|
||||||
|
}
|
||||||
|
| null = null;
|
||||||
|
|
||||||
|
private readonly focusAt$ = LiveData.computed(get => {
|
||||||
|
const selector = get(this.selector$);
|
||||||
|
const mode = get(this.mode$);
|
||||||
|
let id = selector?.blockIds?.[0];
|
||||||
|
let key = 'blockIds';
|
||||||
|
|
||||||
|
if (mode === 'edgeless') {
|
||||||
|
const elementId = selector?.elementIds?.[0];
|
||||||
|
if (elementId) {
|
||||||
|
id = elementId;
|
||||||
|
key = 'elementIds';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!id) return null;
|
||||||
|
|
||||||
|
return { id, key, mode, refreshKey: selector?.refreshKey };
|
||||||
|
});
|
||||||
|
|
||||||
isPresenting$ = new LiveData<boolean>(false);
|
isPresenting$ = new LiveData<boolean>(false);
|
||||||
|
|
||||||
@@ -61,10 +90,6 @@ export class Editor extends Entity {
|
|||||||
this.mode$.next(mode);
|
this.mode$.next(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
setEditorContainer(editorContainer: AffineEditorContainer | null) {
|
|
||||||
this.editorContainer$.next(editorContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
setDefaultOpenProperty(defaultOpenProperty: DefaultOpenProperty | undefined) {
|
setDefaultOpenProperty(defaultOpenProperty: DefaultOpenProperty | undefined) {
|
||||||
this.defaultOpenProperty$.next(defaultOpenProperty);
|
this.defaultOpenProperty$.next(defaultOpenProperty);
|
||||||
}
|
}
|
||||||
@@ -73,6 +98,12 @@ export class Editor extends Entity {
|
|||||||
* sync editor params with view query string
|
* sync editor params with view query string
|
||||||
*/
|
*/
|
||||||
bindWorkbenchView(view: WorkbenchView) {
|
bindWorkbenchView(view: WorkbenchView) {
|
||||||
|
if (this.workbenchView) {
|
||||||
|
throw new Error('already bound');
|
||||||
|
}
|
||||||
|
this.workbenchView = view;
|
||||||
|
this.defaultScrollPosition = view.getScrollPosition() ?? null;
|
||||||
|
|
||||||
const stablePrimaryMode = this.doc.getPrimaryMode();
|
const stablePrimaryMode = this.doc.getPrimaryMode();
|
||||||
|
|
||||||
// eslint-disable-next-line rxjs/finnish
|
// eslint-disable-next-line rxjs/finnish
|
||||||
@@ -147,6 +178,7 @@ export class Editor extends Entity {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
this.workbenchView = null;
|
||||||
unsubscribeEditorParams.unsubscribe();
|
unsubscribeEditorParams.unsubscribe();
|
||||||
unsubscribeViewParams.unsubscribe();
|
unsubscribeViewParams.unsubscribe();
|
||||||
};
|
};
|
||||||
@@ -154,42 +186,95 @@ export class Editor extends Entity {
|
|||||||
|
|
||||||
bindEditorContainer(
|
bindEditorContainer(
|
||||||
editorContainer: AffineEditorContainer,
|
editorContainer: AffineEditorContainer,
|
||||||
docTitle: DocTitle | null
|
docTitle?: DocTitle | null,
|
||||||
|
scrollViewport?: HTMLElement | null
|
||||||
) {
|
) {
|
||||||
|
if (this.editorContainer$.value) {
|
||||||
|
throw new Error('already bound');
|
||||||
|
}
|
||||||
|
this.editorContainer$.next(editorContainer);
|
||||||
const unsubs: (() => void)[] = [];
|
const unsubs: (() => void)[] = [];
|
||||||
|
|
||||||
const focusAt$ = LiveData.computed(get => {
|
const rootService = editorContainer.host?.std.getService('affine:page');
|
||||||
const selector = get(this.selector$);
|
|
||||||
const mode = get(this.mode$);
|
|
||||||
let id = selector?.blockIds?.[0];
|
|
||||||
let key = 'blockIds';
|
|
||||||
|
|
||||||
if (mode === 'edgeless') {
|
// ----- Scroll Position and Selection -----
|
||||||
const elementId = selector?.elementIds?.[0];
|
if (this.defaultScrollPosition) {
|
||||||
if (elementId) {
|
// if we have default scroll position, we should restore it
|
||||||
id = elementId;
|
if (
|
||||||
key = 'elementIds';
|
this.mode$.value === 'page' &&
|
||||||
}
|
typeof this.defaultScrollPosition === 'number'
|
||||||
|
) {
|
||||||
|
scrollViewport?.scrollTo(0, this.defaultScrollPosition || 0);
|
||||||
|
} else if (
|
||||||
|
this.mode$.value === 'edgeless' &&
|
||||||
|
typeof this.defaultScrollPosition === 'object' &&
|
||||||
|
rootService instanceof EdgelessRootService
|
||||||
|
) {
|
||||||
|
rootService.viewport.setViewport(this.defaultScrollPosition.zoom, [
|
||||||
|
this.defaultScrollPosition.centerX,
|
||||||
|
this.defaultScrollPosition.centerY,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!id) return null;
|
this.defaultScrollPosition = null; // reset default scroll position
|
||||||
|
} else {
|
||||||
|
const initialFocusAt = this.focusAt$.value;
|
||||||
|
|
||||||
return { id, key, mode, refreshKey: selector?.refreshKey };
|
if (initialFocusAt === null) {
|
||||||
});
|
const title = docTitle?.querySelector<
|
||||||
if (focusAt$.value === null && docTitle) {
|
|
||||||
const title = docTitle.querySelector<
|
|
||||||
HTMLElement & { inlineEditor: InlineEditor | null }
|
HTMLElement & { inlineEditor: InlineEditor | null }
|
||||||
>('rich-text');
|
>('rich-text');
|
||||||
title?.inlineEditor?.focusEnd();
|
title?.inlineEditor?.focusEnd();
|
||||||
|
} else {
|
||||||
|
const selection = editorContainer.host?.std.selection;
|
||||||
|
|
||||||
|
const { id, key, mode } = initialFocusAt;
|
||||||
|
|
||||||
|
if (mode === this.mode$.value) {
|
||||||
|
selection?.setGroup('scene', [
|
||||||
|
selection?.create('highlight', {
|
||||||
|
mode,
|
||||||
|
[key]: [id],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const subscription = focusAt$
|
// update scroll position when scrollViewport scroll
|
||||||
|
const saveScrollPosition = () => {
|
||||||
|
if (this.mode$.value === 'page' && scrollViewport) {
|
||||||
|
this.workbenchView?.setScrollPosition(scrollViewport.scrollTop);
|
||||||
|
} else if (
|
||||||
|
this.mode$.value === 'edgeless' &&
|
||||||
|
rootService instanceof EdgelessRootService
|
||||||
|
) {
|
||||||
|
this.workbenchView?.setScrollPosition({
|
||||||
|
centerX: rootService.viewport.centerX,
|
||||||
|
centerY: rootService.viewport.centerY,
|
||||||
|
zoom: rootService.viewport.zoom,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
scrollViewport?.addEventListener('scroll', saveScrollPosition);
|
||||||
|
unsubs.push(() => {
|
||||||
|
scrollViewport?.removeEventListener('scroll', saveScrollPosition);
|
||||||
|
});
|
||||||
|
if (rootService instanceof EdgelessRootService) {
|
||||||
|
unsubs.push(
|
||||||
|
rootService.viewport.viewportUpdated.on(saveScrollPosition).dispose
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update selection when focusAt$ changed
|
||||||
|
const subscription = this.focusAt$
|
||||||
.distinctUntilChanged(
|
.distinctUntilChanged(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
a?.id === b?.id &&
|
a?.id === b?.id &&
|
||||||
a?.key === b?.key &&
|
a?.key === b?.key &&
|
||||||
a?.refreshKey === b?.refreshKey
|
a?.refreshKey === b?.refreshKey
|
||||||
)
|
)
|
||||||
|
.pipe(skip(1))
|
||||||
.subscribe(anchor => {
|
.subscribe(anchor => {
|
||||||
if (!anchor) return;
|
if (!anchor) return;
|
||||||
|
|
||||||
@@ -207,6 +292,7 @@ export class Editor extends Entity {
|
|||||||
});
|
});
|
||||||
unsubs.push(subscription.unsubscribe.bind(subscription));
|
unsubs.push(subscription.unsubscribe.bind(subscription));
|
||||||
|
|
||||||
|
// ----- Presenting -----
|
||||||
const edgelessPage = editorContainer.host?.querySelector(
|
const edgelessPage = editorContainer.host?.querySelector(
|
||||||
'affine-edgeless-root'
|
'affine-edgeless-root'
|
||||||
);
|
);
|
||||||
@@ -226,6 +312,7 @@ export class Editor extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
this.editorContainer$.next(null);
|
||||||
for (const unsub of unsubs) {
|
for (const unsub of unsubs) {
|
||||||
unsub();
|
unsub();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ function DocPeekPreviewEditor({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
editor.setEditorContainer(editorContainer);
|
|
||||||
const unbind = editor.bindEditorContainer(
|
const unbind = editor.bindEditorContainer(
|
||||||
editorContainer,
|
editorContainer,
|
||||||
(editorContainer as any).title
|
(editorContainer as any).title
|
||||||
@@ -120,7 +119,6 @@ function DocPeekPreviewEditor({
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unbind();
|
unbind();
|
||||||
editor.setEditorContainer(null);
|
|
||||||
disposableGroup.dispose();
|
disposableGroup.dispose();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ export class View extends Entity<{
|
|||||||
|
|
||||||
sidebarTabs$ = new LiveData<SidebarTab[]>([]);
|
sidebarTabs$ = new LiveData<SidebarTab[]>([]);
|
||||||
|
|
||||||
|
scrollPositions = new WeakMap<
|
||||||
|
Location,
|
||||||
|
| number
|
||||||
|
| {
|
||||||
|
centerX: number;
|
||||||
|
centerY: number;
|
||||||
|
zoom: number;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
// _activeTabId may point to a non-existent tab.
|
// _activeTabId may point to a non-existent tab.
|
||||||
// In this case, we still retain the activeTabId data and wait for the non-existent tab to be mounted.
|
// In this case, we still retain the activeTabId data and wait for the non-existent tab to be mounted.
|
||||||
_activeSidebarTabId$ = new LiveData<string | null>(null);
|
_activeSidebarTabId$ = new LiveData<string | null>(null);
|
||||||
@@ -161,6 +171,22 @@ export class View extends Entity<{
|
|||||||
this._activeSidebarTabId$.next(id);
|
this._activeSidebarTabId$.next(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getScrollPosition() {
|
||||||
|
return this.scrollPositions.get(this.history.location);
|
||||||
|
}
|
||||||
|
|
||||||
|
setScrollPosition(
|
||||||
|
position:
|
||||||
|
| number
|
||||||
|
| {
|
||||||
|
centerX: number;
|
||||||
|
centerY: number;
|
||||||
|
zoom: number;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
this.scrollPositions.set(this.history.location, position);
|
||||||
|
}
|
||||||
|
|
||||||
setTitle(title: string) {
|
setTitle(title: string) {
|
||||||
this.title$.next(title);
|
this.title$.next(title);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,6 @@ export function useBindWorkbenchToBrowserRouter(
|
|||||||
if (update.action === 'POP') {
|
if (update.action === 'POP') {
|
||||||
// This is because the history of view and browser are two different stacks,
|
// This is because the history of view and browser are two different stacks,
|
||||||
// the POP action cannot be synchronized.
|
// the POP action cannot be synchronized.
|
||||||
throw new Error('POP view history is not allowed on browser');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update.location.state === 'fromBrowser') {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,13 +44,11 @@ export function useBindWorkbenchToBrowserRouter(
|
|||||||
basename
|
basename
|
||||||
);
|
);
|
||||||
|
|
||||||
if (locationIsEqual(browserLocation, newBrowserLocation)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
navigate(newBrowserLocation, {
|
navigate(newBrowserLocation, {
|
||||||
state: 'fromView',
|
state: 'fromView,' + newBrowserLocation.key,
|
||||||
replace: update.action === 'REPLACE',
|
replace:
|
||||||
|
update.action === 'REPLACE' ||
|
||||||
|
newBrowserLocation.state === 'fromBrowser',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [basename, browserLocation, navigate, view]);
|
}, [basename, browserLocation, navigate, view]);
|
||||||
@@ -67,7 +61,25 @@ export function useBindWorkbenchToBrowserRouter(
|
|||||||
if (newLocation === null) {
|
if (newLocation === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
typeof newLocation.state === 'string' &&
|
||||||
|
newLocation.state.startsWith('fromView')
|
||||||
|
) {
|
||||||
|
const fromViewKey = newLocation.state.substring('fromView,'.length);
|
||||||
|
if (fromViewKey === view.location$.value.key) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const target = view.history.entries.findIndex(
|
||||||
|
entry => entry.key === fromViewKey
|
||||||
|
);
|
||||||
|
if (target !== -1) {
|
||||||
|
const now = view.history.index;
|
||||||
|
const delta = target - now;
|
||||||
|
view.history.go(delta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
view.history.push(newLocation, 'fromBrowser');
|
view.history.push(newLocation, 'fromBrowser');
|
||||||
}, [basename, browserLocation, view]);
|
}, [basename, browserLocation, view]);
|
||||||
}
|
}
|
||||||
@@ -94,12 +106,3 @@ function viewLocationToBrowserLocation(
|
|||||||
pathname: `${basename}${location.pathname}`,
|
pathname: `${basename}${location.pathname}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function locationIsEqual(a: Location, b: Location) {
|
|
||||||
return (
|
|
||||||
a.hash === b.hash &&
|
|
||||||
a.pathname === b.pathname &&
|
|
||||||
a.search === b.search &&
|
|
||||||
a.state === b.state
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user