Skip to content

Commit 2fce516

Browse files
Experimental URL improvement.
The mechanism could be simplified quite a bit but trying to keep change isolated to the router for now.
1 parent 6f10a29 commit 2fce516

File tree

1 file changed

+42
-21
lines changed

1 file changed

+42
-21
lines changed

src/router-hooks.tsx

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,34 @@ type RouterContextValue = [
6868

6969
const RouterContext = createContext<RouterContextValue | undefined>(undefined);
7070

71-
const parse = (search: string): RouterState => {
71+
const parse = (pathname: string, search: string): RouterState => {
7272
const params = new URLSearchParams(search);
73-
return {
74-
tab: params.get("tab") ?? undefined,
75-
api: anchorForParam(params.get("api")),
76-
reference: anchorForParam(params.get("reference")),
77-
idea: anchorForParam(params.get("idea")),
78-
};
73+
if (params.get("tab")) {
74+
// Legacy
75+
return {
76+
tab: params.get("tab") ?? undefined,
77+
api: anchorForParam(params.get("api")),
78+
reference: anchorForParam(params.get("reference")),
79+
idea: anchorForParam(params.get("idea")),
80+
};
81+
}
82+
const base = process.env.PUBLIC_URL ?? "/";
83+
pathname = pathname.slice(base.length);
84+
if (pathname) {
85+
const parts = pathname.split("/");
86+
const tab = parts[0];
87+
switch (tab) {
88+
case "api":
89+
return { tab: "api", api: anchorForParam(parts[1]) };
90+
case "reference":
91+
return { tab: "reference", api: anchorForParam(parts[1]) };
92+
case "idea":
93+
return { tab: "ideas", api: anchorForParam(parts[1]) };
94+
default:
95+
return {};
96+
}
97+
}
98+
return {};
7999
};
80100

81101
/**
@@ -95,28 +115,29 @@ export const useRouterState = (): RouterContextValue => {
95115
};
96116

97117
export const toUrl = (state: RouterState): string => {
98-
const query = Object.entries(state)
99-
.filter(([k, v]) => k !== "focus" && !!v)
100-
.map(([k, v]) => {
101-
return `${encodeURIComponent(k)}=${encodeURIComponent(
102-
serializeValue(v)
103-
)}`;
104-
})
105-
.join("&");
106-
return window.location.toString().split("?")[0] + (query ? "?" + query : "");
118+
// This could be cleaned up if we always set the tab.
119+
const parts = [
120+
state.tab ??
121+
(state.tab === "api" || state.api ? "api" : undefined) ??
122+
(state.reference ? "reference" : undefined) ??
123+
(state.idea ? "ideas" : undefined),
124+
state.api?.id ?? state.reference?.id ?? state.idea?.id,
125+
];
126+
const base = process.env.PUBLIC_URL ?? "/";
127+
const pathname = base + parts.filter((x): x is string => !!x).join("/");
128+
return window.location.toString().split("/", 1)[0] + pathname;
107129
};
108130

109-
const serializeValue = (value: Anchor | string) =>
110-
typeof value === "string" ? value : value.id;
111-
112131
export const RouterProvider = ({ children }: { children: ReactNode }) => {
113132
const logging = useLogging();
114-
const [state, setState] = useState(parse(window.location.search));
133+
const [state, setState] = useState(
134+
parse(window.location.pathname, window.location.search)
135+
);
115136
useEffect(() => {
116137
// This detects browser navigation but not our programatic changes,
117138
// so we need to update state there ourselves.
118139
const listener = (_: PopStateEvent) => {
119-
const newState = parse(window.location.search);
140+
const newState = parse(window.location.pathname, window.location.search);
120141
setState(newState);
121142
};
122143
window.addEventListener("popstate", listener);

0 commit comments

Comments
 (0)