2024-07-30 23:33:33 +08:00
|
|
|
import { type PageRef, parsePageRef } from "../plug-api/lib/page_ref.ts";
|
|
|
|
import type { Client } from "./client.ts";
|
2024-08-07 02:11:38 +08:00
|
|
|
import { cleanPageRef } from "@silverbulletmd/silverbullet/lib/resolve";
|
2024-02-09 04:00:45 +08:00
|
|
|
import { renderTheTemplate } from "$common/syscalls/template.ts";
|
|
|
|
import { safeRun } from "../lib/async.ts";
|
2022-03-20 16:56:28 +08:00
|
|
|
|
2024-01-24 18:58:33 +08:00
|
|
|
export type PageState = PageRef & {
|
|
|
|
scrollTop?: number;
|
|
|
|
selection?: {
|
|
|
|
anchor: number;
|
|
|
|
head?: number;
|
|
|
|
};
|
|
|
|
};
|
2022-03-20 16:56:28 +08:00
|
|
|
|
2022-03-28 21:25:05 +08:00
|
|
|
export class PathPageNavigator {
|
|
|
|
navigationResolve?: () => void;
|
2024-02-03 02:19:07 +08:00
|
|
|
indexPage!: string;
|
2022-03-28 21:25:05 +08:00
|
|
|
|
2024-01-24 18:58:33 +08:00
|
|
|
openPages = new Map<string, PageState>();
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
private client: Client,
|
|
|
|
) {
|
2024-02-03 02:19:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
async init() {
|
2024-01-24 18:58:33 +08:00
|
|
|
this.indexPage = cleanPageRef(
|
2024-02-03 02:19:07 +08:00
|
|
|
await renderTheTemplate(
|
2024-08-02 22:47:36 +08:00
|
|
|
this.client.config.indexPage,
|
2024-02-03 02:19:07 +08:00
|
|
|
{},
|
|
|
|
{},
|
2024-03-05 02:50:37 +08:00
|
|
|
this.client.stateDataStore.functionMap,
|
2024-02-03 02:19:07 +08:00
|
|
|
),
|
2024-01-24 18:58:33 +08:00
|
|
|
);
|
|
|
|
}
|
2022-07-22 19:44:28 +08:00
|
|
|
|
2024-01-24 18:58:33 +08: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 18:01:34 +08:00
|
|
|
async navigate(
|
2024-01-24 18:58:33 +08:00
|
|
|
pageRef: PageRef,
|
2024-01-02 18:01:34 +08:00
|
|
|
replaceState = false,
|
|
|
|
) {
|
2024-01-24 18:58:33 +08:00
|
|
|
if (pageRef.page === this.indexPage) {
|
|
|
|
pageRef.page = "";
|
2022-08-02 18:43:39 +08:00
|
|
|
}
|
2024-01-24 18:58:33 +08: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) {
|
2022-08-01 21:06:32 +08:00
|
|
|
window.history.replaceState(
|
2024-01-24 18:58:33 +08:00
|
|
|
cleanState,
|
|
|
|
"",
|
2024-08-18 16:39:04 +08:00
|
|
|
`/${encodeURIComponent(currentState.page)}`,
|
2022-08-01 21:06:32 +08:00
|
|
|
);
|
|
|
|
window.history.pushState(
|
2024-01-24 18:58:33 +08:00
|
|
|
pageRef,
|
|
|
|
"",
|
2024-08-18 16:39:04 +08:00
|
|
|
`/${encodeURIComponent(pageRef.page)}`,
|
2024-01-24 18:58:33 +08:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
window.history.replaceState(
|
|
|
|
pageRef,
|
|
|
|
"",
|
2024-08-18 16:39:04 +08:00
|
|
|
`/${encodeURIComponent(pageRef.page)}`,
|
2022-08-01 21:06:32 +08:00
|
|
|
);
|
|
|
|
}
|
2023-06-14 02:47:05 +08:00
|
|
|
globalThis.dispatchEvent(
|
2022-04-01 21:02:35 +08:00
|
|
|
new PopStateEvent("popstate", {
|
2024-01-24 18:58:33 +08:00
|
|
|
state: pageRef,
|
2022-10-10 20:50:21 +08:00
|
|
|
}),
|
2022-03-28 21:25:05 +08:00
|
|
|
);
|
|
|
|
await new Promise<void>((resolve) => {
|
2022-03-20 16:56:28 +08:00
|
|
|
this.navigationResolve = resolve;
|
|
|
|
});
|
|
|
|
this.navigationResolve = undefined;
|
|
|
|
}
|
2022-03-28 21:25:05 +08:00
|
|
|
|
2024-01-24 18:58:33 +08:00
|
|
|
buildCurrentPageState(): PageState {
|
2024-01-24 21:44:39 +08:00
|
|
|
const pageState: PageState = parsePageRefFromURI();
|
2024-01-24 18:58:33 +08:00
|
|
|
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 21:25:05 +08:00
|
|
|
subscribe(
|
2024-01-02 18:01:34 +08:00
|
|
|
pageLoadCallback: (
|
2024-01-24 18:58:33 +08:00
|
|
|
pageState: PageState,
|
2024-01-02 18:01:34 +08:00
|
|
|
) => Promise<void>,
|
2022-03-28 21:25:05 +08:00
|
|
|
): void {
|
2024-01-24 18:58:33 +08:00
|
|
|
const cb = (event: PopStateEvent) => {
|
2022-04-01 21:03:12 +08:00
|
|
|
safeRun(async () => {
|
2024-01-24 18:58:33 +08:00
|
|
|
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 21:44:39 +08:00
|
|
|
const pageRef = parsePageRefFromURI();
|
2024-01-24 18:58:33 +08:00
|
|
|
if (!pageRef.page) {
|
|
|
|
pageRef.page = this.indexPage;
|
|
|
|
}
|
|
|
|
await pageLoadCallback(pageRef);
|
|
|
|
}
|
2022-04-01 21:03:12 +08:00
|
|
|
if (this.navigationResolve) {
|
|
|
|
this.navigationResolve();
|
2022-03-20 16:56:28 +08:00
|
|
|
}
|
2022-04-01 21:03:12 +08:00
|
|
|
});
|
|
|
|
};
|
2023-06-14 02:47:05 +08:00
|
|
|
globalThis.addEventListener("popstate", cb);
|
2024-01-24 18:58:33 +08:00
|
|
|
|
|
|
|
cb(
|
|
|
|
new PopStateEvent("popstate", {
|
|
|
|
state: this.buildCurrentPageState(),
|
|
|
|
}),
|
|
|
|
);
|
2022-03-20 16:56:28 +08:00
|
|
|
}
|
2024-01-24 21:44:39 +08:00
|
|
|
}
|
2022-03-20 16:56:28 +08:00
|
|
|
|
2024-01-24 21:44:39 +08:00
|
|
|
export function parsePageRefFromURI(): PageRef {
|
2024-08-18 16:39:04 +08:00
|
|
|
const pageRef = parsePageRef(decodeURIComponent(
|
2024-01-24 21:44:39 +08:00
|
|
|
location.pathname.substring(1),
|
|
|
|
));
|
2022-03-20 16:56:28 +08:00
|
|
|
|
2024-01-25 21:51:40 +08:00
|
|
|
if (location.hash) {
|
2024-08-18 16:39:04 +08:00
|
|
|
pageRef.header = decodeURIComponent(location.hash.substring(1));
|
2024-01-25 21:51:40 +08:00
|
|
|
}
|
|
|
|
|
2024-01-24 21:44:39 +08:00
|
|
|
return pageRef;
|
2022-03-20 16:56:28 +08:00
|
|
|
}
|