Skip to content

Commit 501c3c4

Browse files
authored
normalize pages (#1111)
* normalize pages * remove unused import
1 parent 52589e8 commit 501c3c4

File tree

6 files changed

+88
-45
lines changed

6 files changed

+88
-45
lines changed

src/config.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {LoaderResolver} from "./dataloader.js";
99
import {visitMarkdownFiles} from "./files.js";
1010
import {formatIsoDate, formatLocaleDate} from "./format.js";
1111
import {createMarkdownIt, parseMarkdown} from "./markdown.js";
12-
import {resolvePath} from "./path.js";
12+
import {isAssetPath, parseRelativeUrl, resolvePath} from "./path.js";
1313
import {resolveTheme} from "./theme.js";
1414

1515
export interface Page {
@@ -197,7 +197,13 @@ function normalizePage(spec: any): Page {
197197
let {name, path} = spec;
198198
name = String(name);
199199
path = String(path);
200-
if (path.endsWith("/")) path = `${path}index`;
200+
if (isAssetPath(path)) {
201+
const u = parseRelativeUrl(join("/", path)); // add leading slash
202+
let {pathname} = u;
203+
pathname = pathname.replace(/\.html$/i, ""); // remove trailing .html
204+
pathname = pathname.replace(/\/$/, "/index"); // add trailing index
205+
path = pathname + u.search + u.hash;
206+
}
201207
return {name, path};
202208
}
203209

src/markdown.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {rewriteHtmlPaths} from "./html.js";
1414
import {parseInfo} from "./info.js";
1515
import type {JavaScriptNode} from "./javascript/parse.js";
1616
import {parseJavaScript} from "./javascript/parse.js";
17-
import {relativePath} from "./path.js";
17+
import {isAssetPath, parseRelativeUrl, relativePath} from "./path.js";
1818
import {transpileSql} from "./sql.js";
1919
import {transpileTag} from "./tag.js";
2020
import {InvalidThemeError} from "./theme.js";
@@ -280,22 +280,10 @@ function makeSoftbreakRenderer(baseRenderer: RenderRule): RenderRule {
280280
};
281281
}
282282

283-
export function parseRelativeUrl(url: string): {pathname: string; search: string; hash: string} {
284-
let search: string;
285-
let hash: string;
286-
const i = url.indexOf("#");
287-
if (i < 0) hash = "";
288-
else (hash = url.slice(i)), (url = url.slice(0, i));
289-
const j = url.indexOf("?");
290-
if (j < 0) search = "";
291-
else (search = url.slice(j)), (url = url.slice(0, j));
292-
return {pathname: url, search, hash};
293-
}
294-
295283
export function makeLinkNormalizer(baseNormalize: (url: string) => string, clean: boolean): (url: string) => string {
296284
return (url) => {
297-
// Only clean relative links; ignore e.g. "https:" links.
298-
if (!/^\w+:/.test(url)) {
285+
// Only clean local links (and ignore e.g. "https:" links).
286+
if (isAssetPath(url)) {
299287
const u = parseRelativeUrl(url);
300288
let {pathname} = u;
301289
if (pathname && !pathname.endsWith("/") && !extname(pathname)) pathname += ".html";

src/path.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,15 @@ export function isAssetPath(specifier: string): boolean {
7272
export function resolveRelativePath(source: string, target: string): string {
7373
return relativePath(source, resolvePath(source, target));
7474
}
75+
76+
export function parseRelativeUrl(url: string): {pathname: string; search: string; hash: string} {
77+
let search: string;
78+
let hash: string;
79+
const i = url.indexOf("#");
80+
if (i < 0) hash = "";
81+
else (hash = url.slice(i)), (url = url.slice(0, i));
82+
const j = url.indexOf("?");
83+
if (j < 0) search = "";
84+
else (search = url.slice(j)), (url = url.slice(0, j));
85+
return {pathname: url, search, hash};
86+
}

test/config-test.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,57 @@ describe("normalizeConfig(spec, root)", () => {
8383
it("coerces pages to an array", async () => {
8484
assert.deepStrictEqual((await config({pages: new Set()}, root)).pages, []);
8585
});
86-
it("coerces pages", async () => {
86+
it("coerces and normalizes page paths", async () => {
8787
const inpages = [
8888
{name: 42, path: true},
89-
{name: null, path: {toString: () => "yes"}}
89+
{name: null, path: {toString: () => "yes"}},
90+
{name: "Index", path: "/foo/index"},
91+
{name: "Index.html", path: "/foo/index.html"},
92+
{name: "Page.html", path: "/foo.html"}
9093
];
9194
const outpages = [
92-
{name: "42", path: "true"},
93-
{name: "null", path: "yes"}
95+
{name: "42", path: "/true"},
96+
{name: "null", path: "/yes"},
97+
{name: "Index", path: "/foo/index"},
98+
{name: "Index.html", path: "/foo/index"},
99+
{name: "Page.html", path: "/foo"}
100+
];
101+
assert.deepStrictEqual((await config({pages: inpages}, root)).pages, outpages);
102+
});
103+
it("allows external page paths", async () => {
104+
const pages = [{name: "Example.com", path: "https://example.com"}];
105+
assert.deepStrictEqual((await config({pages}, root)).pages, pages);
106+
});
107+
it("allows page paths to have query strings and anchor fragments", async () => {
108+
const inpages = [
109+
{name: "Anchor fragment on index", path: "/test/index#foo=bar"},
110+
{name: "Anchor fragment on index.html", path: "/test/index.html#foo=bar"},
111+
{name: "Anchor fragment on page.html", path: "/test.html#foo=bar"},
112+
{name: "Anchor fragment on slash", path: "/test/#foo=bar"},
113+
{name: "Anchor fragment", path: "/test#foo=bar"},
114+
{name: "Query string on index", path: "/test/index?foo=bar"},
115+
{name: "Query string on index.html", path: "/test/index.html?foo=bar"},
116+
{name: "Query string on page.html", path: "/test.html?foo=bar"},
117+
{name: "Query string on slash", path: "/test/?foo=bar"},
118+
{name: "Query string", path: "/test?foo=bar"}
119+
];
120+
const outpages = [
121+
{name: "Anchor fragment on index", path: "/test/index#foo=bar"},
122+
{name: "Anchor fragment on index.html", path: "/test/index#foo=bar"},
123+
{name: "Anchor fragment on page.html", path: "/test#foo=bar"},
124+
{name: "Anchor fragment on slash", path: "/test/index#foo=bar"},
125+
{name: "Anchor fragment", path: "/test#foo=bar"},
126+
{name: "Query string on index", path: "/test/index?foo=bar"},
127+
{name: "Query string on index.html", path: "/test/index?foo=bar"},
128+
{name: "Query string on page.html", path: "/test?foo=bar"},
129+
{name: "Query string on slash", path: "/test/index?foo=bar"},
130+
{name: "Query string", path: "/test?foo=bar"}
94131
];
95132
assert.deepStrictEqual((await config({pages: inpages}, root)).pages, outpages);
96133
});
97134
it("coerces sections", async () => {
98135
const inpages = [{name: 42, pages: new Set([{name: null, path: {toString: () => "yes"}}])}];
99-
const outpages = [{name: "42", open: true, pages: [{name: "null", path: "yes"}]}];
136+
const outpages = [{name: "42", open: true, pages: [{name: "null", path: "/yes"}]}];
100137
assert.deepStrictEqual((await config({pages: inpages}, root)).pages, outpages);
101138
});
102139
it("coerces toc", async () => {

test/markdown-test.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import deepEqual from "fast-deep-equal";
66
import {normalizeConfig} from "../src/config.js";
77
import {isEnoent} from "../src/error.js";
88
import type {MarkdownPage} from "../src/markdown.js";
9-
import {makeLinkNormalizer, parseMarkdown, parseRelativeUrl} from "../src/markdown.js";
9+
import {makeLinkNormalizer, parseMarkdown} from "../src/markdown.js";
1010

1111
const {md} = await normalizeConfig();
1212

@@ -63,27 +63,6 @@ describe("parseMarkdown(input)", () => {
6363
}
6464
});
6565

66-
describe("parseRelativeUrl(url)", () => {
67-
it("handles paths", () => {
68-
assert.deepStrictEqual(parseRelativeUrl("foo"), {pathname: "foo", search: "", hash: ""});
69-
assert.deepStrictEqual(parseRelativeUrl("foo.html"), {pathname: "foo.html", search: "", hash: ""});
70-
assert.deepStrictEqual(parseRelativeUrl("../foo"), {pathname: "../foo", search: "", hash: ""});
71-
assert.deepStrictEqual(parseRelativeUrl("./foo"), {pathname: "./foo", search: "", hash: ""});
72-
assert.deepStrictEqual(parseRelativeUrl("/foo"), {pathname: "/foo", search: "", hash: ""});
73-
assert.deepStrictEqual(parseRelativeUrl("/foo%3Fbar"), {pathname: "/foo%3Fbar", search: "", hash: ""});
74-
});
75-
it("handles queries", () => {
76-
assert.deepStrictEqual(parseRelativeUrl("foo?bar"), {pathname: "foo", search: "?bar", hash: ""});
77-
});
78-
it("handles hashes", () => {
79-
assert.deepStrictEqual(parseRelativeUrl("foo#bar"), {pathname: "foo", search: "", hash: "#bar"});
80-
assert.deepStrictEqual(parseRelativeUrl("foo#bar?baz"), {pathname: "foo", search: "", hash: "#bar?baz"});
81-
});
82-
it("handles queries and hashes", () => {
83-
assert.deepStrictEqual(parseRelativeUrl("foo?bar#baz"), {pathname: "foo", search: "?bar", hash: "#baz"});
84-
});
85-
});
86-
8766
describe("makeLinkNormalizer(normalize, false)", () => {
8867
const normalize = makeLinkNormalizer(String, false);
8968
it("appends .html to extension-less links", () => {

test/path-test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from "node:assert";
2-
import {isPathImport, relativePath, resolveLocalPath, resolvePath} from "../src/path.js";
2+
import {isPathImport, parseRelativeUrl, relativePath, resolveLocalPath, resolvePath} from "../src/path.js";
33

44
describe("resolvePath(source, target)", () => {
55
it("returns the path to the specified target within the source root", () => {
@@ -127,3 +127,24 @@ describe("isPathImport(specifier)", () => {
127127
assert.strictEqual(isPathImport("foo://bar"), false);
128128
});
129129
});
130+
131+
describe("parseRelativeUrl(url)", () => {
132+
it("handles paths", () => {
133+
assert.deepStrictEqual(parseRelativeUrl("foo"), {pathname: "foo", search: "", hash: ""});
134+
assert.deepStrictEqual(parseRelativeUrl("foo.html"), {pathname: "foo.html", search: "", hash: ""});
135+
assert.deepStrictEqual(parseRelativeUrl("../foo"), {pathname: "../foo", search: "", hash: ""});
136+
assert.deepStrictEqual(parseRelativeUrl("./foo"), {pathname: "./foo", search: "", hash: ""});
137+
assert.deepStrictEqual(parseRelativeUrl("/foo"), {pathname: "/foo", search: "", hash: ""});
138+
assert.deepStrictEqual(parseRelativeUrl("/foo%3Fbar"), {pathname: "/foo%3Fbar", search: "", hash: ""});
139+
});
140+
it("handles queries", () => {
141+
assert.deepStrictEqual(parseRelativeUrl("foo?bar"), {pathname: "foo", search: "?bar", hash: ""});
142+
});
143+
it("handles hashes", () => {
144+
assert.deepStrictEqual(parseRelativeUrl("foo#bar"), {pathname: "foo", search: "", hash: "#bar"});
145+
assert.deepStrictEqual(parseRelativeUrl("foo#bar?baz"), {pathname: "foo", search: "", hash: "#bar?baz"});
146+
});
147+
it("handles queries and hashes", () => {
148+
assert.deepStrictEqual(parseRelativeUrl("foo?bar#baz"), {pathname: "foo", search: "?bar", hash: "#baz"});
149+
});
150+
});

0 commit comments

Comments
 (0)