1
0
silverbullet/web/navigator.ts

148 lines
4.0 KiB
TypeScript
Raw Permalink Normal View History

import { safeRun } from "../common/util.ts";
import { PageRef, parsePageRef } from "$sb/lib/page.ts";
import { Client } from "./client.ts";
import { cleanPageRef } from "$sb/lib/resolve.ts";
import { renderHandlebarsTemplate } from "../common/syscalls/handlebars.ts";
export type PageState = PageRef & {
scrollTop?: number;
selection?: {
anchor: number;
head?: number;
};
};
2022-03-28 13:25:05 +00:00
export class PathPageNavigator {
navigationResolve?: () => void;
indexPage: string;
2022-03-28 13:25:05 +00:00
openPages = new Map<string, PageState>();
constructor(
private client: Client,
) {
this.indexPage = cleanPageRef(
renderHandlebarsTemplate(client.settings.indexPage, {}, {}),
);
}
2022-07-22 11:44:28 +00:00
/**
* Navigates the client to the given page, this involves:
* - Patching the current popstate with current state
* - Pushing the new state
* - Dispatching a popstate event
* @param pageRef to navigate to
* @param replaceState whether to update the state in place (rather than to push a new state)
*/
2024-01-02 10:01:34 +00:00
async navigate(
pageRef: PageRef,
2024-01-02 10:01:34 +00:00
replaceState = false,
) {
if (pageRef.page === this.indexPage) {
pageRef.page = "";
2022-08-02 10:43:39 +00:00
}
const currentState = this.buildCurrentPageState();
// No need to keep pos and anchor if we already have scrollTop and selection
const cleanState = { ...currentState, pos: undefined, anchor: undefined };
this.openPages.set(currentState.page || this.indexPage, cleanState);
if (!replaceState) {
window.history.replaceState(
cleanState,
"",
2024-01-24 13:44:39 +00:00
`/${currentState.page}`,
);
window.history.pushState(
pageRef,
"",
2024-01-24 13:44:39 +00:00
`/${pageRef.page}`,
);
} else {
window.history.replaceState(
pageRef,
"",
2024-01-24 13:44:39 +00:00
`/${pageRef.page}`,
);
}
globalThis.dispatchEvent(
2022-04-01 13:02:35 +00:00
new PopStateEvent("popstate", {
state: pageRef,
}),
2022-03-28 13:25:05 +00:00
);
await new Promise<void>((resolve) => {
this.navigationResolve = resolve;
});
this.navigationResolve = undefined;
}
2022-03-28 13:25:05 +00:00
buildCurrentPageState(): PageState {
2024-01-24 13:44:39 +00:00
const pageState: PageState = parsePageRefFromURI();
const mainSelection = this.client.editorView.state.selection.main;
pageState.scrollTop = this.client.editorView.scrollDOM.scrollTop;
pageState.selection = {
head: mainSelection.head,
anchor: mainSelection.anchor,
};
return pageState;
}
2022-03-28 13:25:05 +00:00
subscribe(
2024-01-02 10:01:34 +00:00
pageLoadCallback: (
pageState: PageState,
2024-01-02 10:01:34 +00:00
) => Promise<void>,
2022-03-28 13:25:05 +00:00
): void {
const cb = (event: PopStateEvent) => {
2022-04-01 13:03:12 +00:00
safeRun(async () => {
const popState = event.state;
if (popState) {
// This is the usual case
if (!popState.page) {
popState.page = this.indexPage;
}
if (
popState.anchor === undefined && popState.pos === undefined &&
popState.selection === undefined &&
popState.scrollTop === undefined
) {
// Pretty low-context popstate, so let's leverage openPages
const openPage = this.openPages.get(popState.page);
if (openPage) {
popState.selection = openPage.selection;
popState.scrollTop = openPage.scrollTop;
}
}
await pageLoadCallback(popState);
} else {
// This occurs when the page is loaded completely fresh with no browser history around it
2024-01-24 13:44:39 +00:00
const pageRef = parsePageRefFromURI();
if (!pageRef.page) {
pageRef.page = this.indexPage;
}
await pageLoadCallback(pageRef);
}
2022-04-01 13:03:12 +00:00
if (this.navigationResolve) {
this.navigationResolve();
}
2022-04-01 13:03:12 +00:00
});
};
globalThis.addEventListener("popstate", cb);
cb(
new PopStateEvent("popstate", {
state: this.buildCurrentPageState(),
}),
);
}
2024-01-24 13:44:39 +00:00
}
2024-01-24 13:44:39 +00:00
export function parsePageRefFromURI(): PageRef {
const pageRef = parsePageRef(decodeURI(
location.pathname.substring(1),
));
2024-01-25 13:51:40 +00:00
if (location.hash) {
pageRef.header = decodeURI(location.hash.substring(1));
}
2024-01-24 13:44:39 +00:00
return pageRef;
}