Skip to content

Commit e3a07ac

Browse files
fix: correct the redirection to the official docs in SymbolsTemplate (#294)
1 parent 2b1f17f commit e3a07ac

File tree

4 files changed

+178
-5
lines changed

4 files changed

+178
-5
lines changed

website/src/components/templates/BaseTemplate.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { FC, PropsWithChildren } from "hono/jsx";
33
import { basePath, originUrl, typstOfficialDocsUrl } from "../../metadata";
44
import { Translation, translation } from "../../translation/";
55
import type { Page } from "../../types/model";
6-
import { joinPath, removeBasePath } from "../../utils/path";
6+
import { joinPath, shiftBase } from "../../utils/path";
77
import { getTranslationStatus } from "../../utils/translationStatus";
88
import {
99
CaretRightCircleIcon,
@@ -50,9 +50,10 @@ export const BaseTemplate: FC<BaseTemplateProps> = ({
5050
joinPath(basePath, "/favicon.png"),
5151
originUrl,
5252
).toString();
53-
const typstOfficialRouteUrl = joinPath(
53+
const typstOfficialRouteUrl = shiftBase(
54+
route,
55+
basePath,
5456
typstOfficialDocsUrl,
55-
removeBasePath(basePath, route),
5657
);
5758
return (
5859
<html lang={translation.htmlLang()} class="scroll-pt-24">

website/src/components/templates/SymbolsTemplate.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { FC } from "hono/jsx";
2+
import { basePath, typstOfficialDocsUrl } from "../../metadata";
23
import type { Page, SymbolsBody } from "../../types/model";
4+
import { shiftBase } from "../../utils/path";
35
import type { BaseTemplateProps } from "./BaseTemplate";
46

57
export type SymbolsTemplateProps = Omit<BaseTemplateProps, "page"> & {
@@ -9,7 +11,7 @@ export type SymbolsTemplateProps = Omit<BaseTemplateProps, "page"> & {
911
};
1012

1113
export const SymbolsTemplate: FC<SymbolsTemplateProps> = ({ page }) => {
12-
const redirectUrl = `https://typst.app${page.route}`;
14+
const redirectUrl = shiftBase(page.route, basePath, typstOfficialDocsUrl);
1315

1416
return (
1517
<html lang="ja">

website/src/utils/path.test.ts

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from "vitest";
2-
import { joinPath, removeBasePath } from "./path";
2+
import { joinPath, removeBasePath, shiftBase } from "./path";
33

44
describe("joinPath", () => {
55
it("should join base and path with single slash", () => {
@@ -119,3 +119,154 @@ describe("removeBasePath", () => {
119119
expect(removeBasePath("", "/foo/bar")).toBe("/foo/bar");
120120
});
121121
});
122+
123+
describe("shiftBase", () => {
124+
describe("normal usages", () => {
125+
it("should handle non-root oldBasePath and full newBaseUrl", () => {
126+
for (const oldBasePath of ["/base", "/base/"]) {
127+
for (const newBaseUrl of [
128+
"https://typst.app/docs",
129+
"https://typst.app/docs/",
130+
]) {
131+
expect(shiftBase("/base/foo/bar/", oldBasePath, newBaseUrl)).toBe(
132+
"https://typst.app/docs/foo/bar/",
133+
);
134+
expect(shiftBase("/base/", oldBasePath, newBaseUrl)).toBe(
135+
"https://typst.app/docs/",
136+
);
137+
}
138+
}
139+
});
140+
141+
it("should handle root oldBasePath and full newBaseUrl", () => {
142+
for (const oldBasePath of ["/", ""]) {
143+
for (const newBaseUrl of [
144+
"https://typst.app/docs",
145+
"https://typst.app/docs/",
146+
]) {
147+
expect(shiftBase("/foo/bar/", oldBasePath, newBaseUrl)).toBe(
148+
"https://typst.app/docs/foo/bar/",
149+
);
150+
expect(shiftBase("/", oldBasePath, newBaseUrl)).toBe(
151+
"https://typst.app/docs/",
152+
);
153+
}
154+
}
155+
});
156+
157+
it("should handle non-root oldBasePath and newBaseUrl without origin", () => {
158+
for (const oldBasePath of ["/ja-JP/docs", "/ja-JP/docs/"]) {
159+
for (const newBaseUrl of ["/en-US/docs", "/en-US/docs/"]) {
160+
expect(
161+
shiftBase("/ja-JP/docs/foo/bar/", oldBasePath, newBaseUrl),
162+
).toBe("/en-US/docs/foo/bar/");
163+
expect(shiftBase("/ja-JP/docs/", oldBasePath, newBaseUrl)).toBe(
164+
"/en-US/docs/",
165+
);
166+
}
167+
}
168+
});
169+
});
170+
171+
// The following tests are generated by AI automatically. They describe the behaviors in edge cases.
172+
// However, these behaviors are not actually used, and their result and may not meet actual needs.
173+
// Therefore, the following usages should be avoided in practice.
174+
describe("generated usages", () => {
175+
it("should handle edge cases with empty oldBasePath and newBaseUrl", () => {
176+
expect(shiftBase("/foo/bar", "", "")).toBe("/foo/bar");
177+
expect(shiftBase("/", "", "")).toBe("/");
178+
expect(shiftBase("", "", "")).toBe("");
179+
});
180+
181+
it("should handle edge cases with empty route", () => {
182+
expect(shiftBase("", "/base", "/new")).toBe("/new/");
183+
expect(shiftBase("", "/", "/new")).toBe("/new/");
184+
expect(shiftBase("", "", "/new")).toBe("/new/");
185+
});
186+
187+
it("should handle edge cases with only slashes", () => {
188+
expect(shiftBase("/", "/", "/new")).toBe("/new/");
189+
expect(shiftBase("/", "/base", "/new")).toBe("/new/");
190+
expect(shiftBase("/base", "/base", "/new")).toBe("/new/");
191+
});
192+
193+
it("should handle routes with duplicate slashes", () => {
194+
expect(shiftBase("/base//foo//bar", "/base", "/new")).toBe(
195+
"/new/foo/bar",
196+
);
197+
expect(shiftBase("//base//foo//", "/base", "/new")).toBe(
198+
"/new/base/foo/",
199+
);
200+
expect(shiftBase("//base//", "/base", "/new")).toBe("/new/base/");
201+
});
202+
203+
it("should handle routes with special characters", () => {
204+
expect(shiftBase("/base/@/foo", "/base", "/new")).toBe("/new/@/foo");
205+
expect(shiftBase("/base/#/foo", "/base", "/new")).toBe("/new/#/foo");
206+
expect(shiftBase("/base/!$/foo", "/base", "/new")).toBe("/new/!$/foo");
207+
});
208+
209+
it("should handle routes with trailing slashes in newBaseUrl", () => {
210+
expect(shiftBase("/base/foo", "/base", "/new/")).toBe("/new/foo");
211+
expect(shiftBase("/base/foo/", "/base", "/new/")).toBe("/new/foo/");
212+
expect(shiftBase("/base/", "/base", "/new/")).toBe("/new/");
213+
});
214+
215+
it("should handle routes with trailing slashes in oldBasePath", () => {
216+
expect(shiftBase("/base/foo", "/base/", "/new")).toBe("/new/foo");
217+
expect(shiftBase("/base/foo/", "/base/", "/new")).toBe("/new/foo/");
218+
expect(shiftBase("/base/", "/base/", "/new")).toBe("/new/");
219+
});
220+
221+
it("should handle routes with mixed slashes and empty parts", () => {
222+
expect(shiftBase("/base//foo", "/base", "/new")).toBe("/new/foo");
223+
expect(shiftBase("/base/foo//", "/base", "/new")).toBe("/new/foo/");
224+
expect(shiftBase("/base//", "/base", "/new")).toBe("/new/");
225+
});
226+
227+
it("should handle newBaseUrl with https://", () => {
228+
expect(shiftBase("/base/foo", "/base", "https://example.com/new")).toBe(
229+
"https://example.com/new/foo",
230+
);
231+
expect(shiftBase("/base/foo/", "/base", "https://example.com/new")).toBe(
232+
"https://example.com/new/foo/",
233+
);
234+
expect(shiftBase("/base/", "/base", "https://example.com/new")).toBe(
235+
"https://example.com/new/",
236+
);
237+
expect(shiftBase("/base", "/base", "https://example.com/new")).toBe(
238+
"https://example.com/new/",
239+
);
240+
});
241+
242+
it("should handle newBaseUrl with //example.com", () => {
243+
expect(shiftBase("/base/foo", "/base", "//example.com/new")).toBe(
244+
"//example.com/new/foo",
245+
);
246+
expect(shiftBase("/base/foo/", "/base", "//example.com/new")).toBe(
247+
"//example.com/new/foo/",
248+
);
249+
expect(shiftBase("/base/", "/base", "//example.com/new")).toBe(
250+
"//example.com/new/",
251+
);
252+
expect(shiftBase("/base", "/base", "//example.com/new")).toBe(
253+
"//example.com/new/",
254+
);
255+
});
256+
257+
it("should handle newBaseUrl with only origin and no path", () => {
258+
expect(shiftBase("/base/foo", "/base", "https://example.com")).toBe(
259+
"https://example.com/foo",
260+
);
261+
expect(shiftBase("/base/foo/", "/base", "https://example.com")).toBe(
262+
"https://example.com/foo/",
263+
);
264+
expect(shiftBase("/base/", "/base", "https://example.com")).toBe(
265+
"https://example.com/",
266+
);
267+
expect(shiftBase("/base", "/base", "https://example.com")).toBe(
268+
"https://example.com/",
269+
);
270+
});
271+
});
272+
});

website/src/utils/path.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,22 @@ export const removeBasePath = (basePath: string, route: string): string => {
6767
const offset = basePath.length - (basePath.endsWith("/") ? 1 : 0);
6868
return route.slice(offset);
6969
};
70+
71+
/**
72+
* Replace the oldBasePath in a page route with a newBaseUrl.
73+
*
74+
* @param route - The route string to process.
75+
* @param oldBasePath - The old base path to be removed from the route.
76+
* @param newBaseUrl - The new base URL (may include origin) to be prepended to the route.
77+
* @returns The route string with its base replaced.
78+
*
79+
* @example
80+
* ```ts
81+
* shiftBase("/base/foo/bar/", "/base/", "https://typst.app/docs/") // -> "https://typst.app/docs/foo/bar/"
82+
* ```
83+
*/
84+
export const shiftBase = (
85+
route: string,
86+
oldBasePath: string,
87+
newBaseUrl: string,
88+
): string => joinPath(newBaseUrl, removeBasePath(oldBasePath, route));

0 commit comments

Comments
 (0)