Fixed edge case in scroll restoration

This commit is contained in:
squidfunk
2020-02-22 15:24:15 +01:00
parent c362179234
commit eb82c8d586
8 changed files with 85 additions and 62 deletions

View File

@@ -51,7 +51,8 @@ import {
pluck,
debounceTime,
distinctUntilKeyChanged,
distinctUntilChanged
distinctUntilChanged,
bufferCount
} from "rxjs/operators"
import {
@@ -310,7 +311,7 @@ export function initialize(config: unknown) {
/**
* Location change
*/
interface LocationChange {
interface State {
url: URL // TODO: use URL!?
data?: ViewportOffset
}
@@ -323,8 +324,8 @@ export function initialize(config: unknown) {
return el.hash.length > 0
}
function compareLocationChange(
{ url: a }: LocationChange, { url: b }: LocationChange
function compareState(
{ url: a }: State, { url: b }: State
) {
return a.href === b.href
}
@@ -360,7 +361,7 @@ export function initialize(config: unknown) {
return NEVER
}),
distinctUntilChanged(),
map<string, LocationChange>(href => ({ url: new URL(href) })),
map<string, State>(href => ({ url: new URL(href) })),
share()
)
@@ -374,8 +375,9 @@ export function initialize(config: unknown) {
/* Intercept popstate events (history back and forward) */
const popstate$ = fromEvent<PopStateEvent>(window, "popstate")
.pipe(
map<PopStateEvent, LocationChange>(ev => ({
url: new URL(getLocation()),
filter(ev => ev.state !== null),
map<PopStateEvent, State>(ev => ({
url: new URL(location.href),
data: ev.state
})),
share()
@@ -391,7 +393,8 @@ export function initialize(config: unknown) {
/* Add dispatched link to history */
internal$
.pipe(
distinctUntilChanged(compareLocationChange),
// TODO: must start with the current location and ignore the first emission
distinctUntilChanged(compareState),
filter(({ url }) => !isAnchorLink(url))
)
.subscribe(({ url }) => {
@@ -399,6 +402,41 @@ export function initialize(config: unknown) {
history.pushState({}, "", url.toString())
})
// special case
merge(internal$, popstate$)
.pipe(
bufferCount(2, 1),
// filter(([prev, next]) => {
// return prev.url.href.match(next.url.href) !== null
// && isAnchorLink(prev.url)
// })
)
.subscribe(([prev, next]) => {
console.log(`<- ${prev.url}`)
console.log(`-> ${next.url}`)
if (
prev.url.href.match(next.url.href) !== null &&
isAnchorLink(prev.url)
) {
dialog$.next(`Potential Candidate: ${JSON.stringify(next.data)}`, ) // awesome debugging.
setViewportOffset(next.data || { y: 0 })
}
// console.log("Potential Candidate")
})
// .subscribe((x) => console.log(x[0].url.toString(), x[1].url.toString()))
// filter(([prev, next]) => {
// return prev.url.href.match(next.url.href) !== null
// && isAnchorLink(prev.url)
// }),
// map(([, next]) => next)
// // distinctUntilChanged(compareLocationChange),
// // filter(({ url }) => !isAnchorLink(url))
// )
// .subscribe(({ url }) => {
// console.log(`Restore ${url}`)
// })
/* Persist viewport offset in history before hash change */
viewport$
.pipe(
@@ -406,29 +444,18 @@ export function initialize(config: unknown) {
distinctUntilKeyChanged("offset"),
)
.subscribe(({ offset }) => {
console.log("Update", offset)
// console.log("Update", offset)
history.replaceState(offset, "")
})
// /* Edge case: go back from anchor to same
// // // TODO: better to just replace the state when this is encountered.
// // pop$
// // .pipe(
// // filter(({ href }) => !/#/.test(href)) // TODO: kind of sucks
// // )
// // .subscribe(({ data }) => {
// // // console.log("Detected", data) // detects too much...
// // setViewportOffset(data || { y: 0 }) // TOOD: must wait for document sample!
// // })
/* */
merge(dispatch$, popstate$)
.pipe(
sample(document$),
withLatestFrom(document$),
)
.subscribe(([{ url: href, data }, { title, head }]) => {
console.log("Done", href.href, data)
.subscribe(([{ url, data }, { title, head }]) => {
console.log("Done", url.href, data)
// setDocumentTitle
document.title = title
@@ -471,11 +498,11 @@ export function initialize(config: unknown) {
// })
// dispatch$.subscribe(({ url }) => {
// console.log(`Push ${url}`)
// console.log(`Dispatch ${url}`)
// })
popstate$.subscribe(({ url }) => {
console.log(`Pop ${url}`)
console.log(`Popstate ${url.href}`, url)
})
}

View File

@@ -24,9 +24,7 @@ import { NEVER, Observable } from "rxjs"
import { ajax } from "rxjs/ajax"
import {
catchError,
distinctUntilChanged,
filter,
map,
distinctUntilKeyChanged,
pluck,
share,
skip,
@@ -54,7 +52,7 @@ interface WatchOptions {
/**
* Watch document switch
*
* This function returns an observables that fetches a document if the provided
* This function returns an observables that fetches a document if the provided // TODO: update docs
* location observable emits a new value (i.e. URL). If the emitted URL points
* to the same page, the request is effectively ignored (i.e. when only the
* fragment identifier changes).
@@ -70,22 +68,20 @@ export function watchDocumentSwitch(
): Observable<Document> {
return location$
.pipe(
startWith(location), // TODO: getLocation should return URL or Location
filter(url => url.hash.length === 0), // use isAnchorLink
map(url => url.href),
distinctUntilChanged(),
startWith(location), // TODO: getLocation should return URL or Location
distinctUntilKeyChanged("pathname"),
skip(1),
/* Fetch document */
switchMap(url => ajax({
url,
url: url.href,
responseType: "document",
withCredentials: true
})
.pipe<Document, Document>(
pluck("response"),
catchError(() => {
setLocation(url)
setLocation(url.href) // TODO: setLocation should accept URL or location
return NEVER
})
)