@threadlabs/markupkit 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/breadcrumbs.html +1 -1
- package/components/nav.html +6 -6
- package/components/pager.html +1 -1
- package/dist/application.js +3 -3
- package/dist/cli.js +0 -0
- package/dist/client.js +13 -13
- package/dist/resource.d.ts +4 -4
- package/dist/resource.js +18 -18
- package/dist/resource.js.map +1 -1
- package/dist/routes.js +14 -14
- package/dist/server.js +6 -6
- package/dist/types.d.ts +14 -14
- package/package.json +21 -23
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<template component="
|
|
1
|
+
<template component="hk-breadcrumbs">
|
|
2
2
|
<defs>
|
|
3
3
|
<prop name="items" type="list(object({ href: string, label: string, current: string, depth: number, pageName: string, group?: string, ... }))" required>Pages from the top of the trail to the current page.</prop>
|
|
4
4
|
<prop name="label" type="string" default="Breadcrumb">Accessible navigation name.</prop>
|
package/components/nav.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<template component="
|
|
1
|
+
<template component="hk-nav-items">
|
|
2
2
|
<defs>
|
|
3
3
|
<prop name="items" type="list(object({ label: string, current: string, depth: number, href?: string, group?: string, open?: boolean, children?: list(object({ label: string, ... })), ... }))" required>Navigation items, each with its nested items.</prop>
|
|
4
4
|
</defs>
|
|
@@ -7,15 +7,15 @@
|
|
|
7
7
|
<li from:data-depth="$item.depth">
|
|
8
8
|
<template $match><details $when="$item.children" from:open="$item.open">
|
|
9
9
|
<summary><template $match><a $when="$item.href" from:href="$item.href" from:aria-current="$item.current">{$item.label}</a><span $else>{$item.label}</span></template></summary>
|
|
10
|
-
<
|
|
10
|
+
<hk-nav-items from:items="$item.children"></hk-nav-items>
|
|
11
11
|
</details>
|
|
12
12
|
<a $else from:href="$item.href" from:aria-current="$item.current">{$item.label}</a></template>
|
|
13
13
|
</li>
|
|
14
14
|
</template></ul>
|
|
15
15
|
</template>
|
|
16
|
-
<!-- The first three levels of items render inline, each as <
|
|
17
|
-
three levels deep is one instance and the page carries its items once. Each deeper level is an <
|
|
18
|
-
<template component="
|
|
16
|
+
<!-- The first three levels of items render inline, each as <hk-nav-items> renders a level, so a tree up to
|
|
17
|
+
three levels deep is one instance and the page carries its items once. Each deeper level is an <hk-nav-items>. -->
|
|
18
|
+
<template component="hk-nav">
|
|
19
19
|
<defs>
|
|
20
20
|
<prop name="items" type="list(object({ label: string, current: string, depth: number, href?: string, group?: string, open?: boolean, children?: list(object({ label: string, ... })), ... }))" required>Ordered page links: navigation() items, or navigationTree() nodes.</prop>
|
|
21
21
|
<prop name="label" type="string" default="Navigation">Accessible navigation name.</prop>
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
<li from:data-depth="$c.depth">
|
|
36
36
|
<template $match><details $when="$c.children" from:open="$c.open">
|
|
37
37
|
<summary><template $match><a $when="$c.href" from:href="$c.href" from:aria-current="$c.current">{$c.label}</a><span $else>{$c.label}</span></template></summary>
|
|
38
|
-
<
|
|
38
|
+
<hk-nav-items from:items="$c.children"></hk-nav-items>
|
|
39
39
|
</details>
|
|
40
40
|
<a $else from:href="$c.href" from:aria-current="$c.current">{$c.label}</a></template>
|
|
41
41
|
</li>
|
package/components/pager.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<template component="
|
|
1
|
+
<template component="hk-pager">
|
|
2
2
|
<defs>
|
|
3
3
|
<prop name="previous" type="object({ href: string, label: string, current: string, depth: number, pageName: string, group?: string, ... })">The page before this one.</prop>
|
|
4
4
|
<prop name="next" type="object({ href: string, label: string, current: string, depth: number, pageName: string, group?: string, ... })">The page after this one.</prop>
|
package/dist/application.js
CHANGED
|
@@ -17,8 +17,8 @@ import { privateReadResponse } from "./private-api.js";
|
|
|
17
17
|
import { createNavigation, failurePage, mime, pageAction, pageResponse, prerenderedActions, renderRoute, requestReadContext, respond, serverFailure } from "./server.js";
|
|
18
18
|
// Built-in components need no link: a resource that uses one gets it added after its content, so
|
|
19
19
|
// diagnostics keep their line numbers. The import map below resolves it from this package.
|
|
20
|
-
const builtins = [["
|
|
21
|
-
["
|
|
20
|
+
const builtins = [["hk-nav", "@threadlabs/markupkit/components/nav.html"], ["hk-breadcrumbs", "@threadlabs/markupkit/components/breadcrumbs.html"],
|
|
21
|
+
["hk-pager", "@threadlabs/markupkit/components/pager.html"]];
|
|
22
22
|
/** MarkupKit's import map: @/ is the project root, as in Nuxt, and built-ins resolve from this package. */
|
|
23
23
|
function importMap(root) {
|
|
24
24
|
return { imports: { "@/": pathToFileURL(root).href + "/", "@threadlabs/markupkit/components/": new URL("../components/", import.meta.url).href } };
|
|
@@ -126,7 +126,7 @@ export async function createApplicationInternals(input = {}, moduleServer, asset
|
|
|
126
126
|
throw new MarkupKitError(`Static route collision at ${path}.`, route.component);
|
|
127
127
|
const page = pageRedirects.get(path);
|
|
128
128
|
if (page !== undefined)
|
|
129
|
-
throw new MarkupKitError(`The page redirect
|
|
129
|
+
throw new MarkupKitError(`The page redirect hk:redirect-from ${page.from} is the URL of ${route.component}.`, page.component);
|
|
130
130
|
if (redirects.has(path))
|
|
131
131
|
throw new MarkupKitError(`The redirects option also redirects ${path}, which is this page's URL.`, route.component);
|
|
132
132
|
const matched = matchRoute(routes, path, config.base);
|
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/client.js
CHANGED
|
@@ -54,9 +54,9 @@ export function page(runtime, options, pageComponents) {
|
|
|
54
54
|
// Safari 26.2), which carries the per-link opt-outs; earlier Navigation API versions would skip them.
|
|
55
55
|
if ("navigation" in window && "sourceElement" in NavigateEvent.prototype)
|
|
56
56
|
navigate(runtime, options);
|
|
57
|
-
document.dispatchEvent(new Event("
|
|
57
|
+
document.dispatchEvent(new Event("hk:ready"));
|
|
58
58
|
}
|
|
59
|
-
const layer = (root, index) => root.querySelector(`[data-
|
|
59
|
+
const layer = (root, index) => root.querySelector(`[data-hk-layer="${index}"]`) ?? root.getElementById(`hk-layer-${index}`);
|
|
60
60
|
const fresh = (time) => performance.now() - time < 30_000;
|
|
61
61
|
const loaded = async (url, signal) => {
|
|
62
62
|
const response = await fetch(url, signal === undefined ? {} : { signal });
|
|
@@ -108,7 +108,7 @@ async function render(runtime, payload) {
|
|
|
108
108
|
const component = components.get(tag);
|
|
109
109
|
if (component === undefined)
|
|
110
110
|
throw new Error(`MarkupKit cannot render <${tag}>: no page module defines it.`);
|
|
111
|
-
const attributes = { id: `
|
|
111
|
+
const attributes = { id: `hk-layer-${index}`, "data-hk-layer": String(index), ...index === 0 ? {} : { slot: "page" } };
|
|
112
112
|
const html = {};
|
|
113
113
|
for (const [name, value] of Object.entries(authored)) {
|
|
114
114
|
const prop = component[1][name];
|
|
@@ -147,7 +147,7 @@ function navigate(runtime, { base, prefetch: policy, manifest }) {
|
|
|
147
147
|
const shell = (head) => [...head.querySelectorAll("link:not([data-component-head])")];
|
|
148
148
|
let owned = shell(document.head);
|
|
149
149
|
const announcer = document.createElement("div");
|
|
150
|
-
announcer.id = "
|
|
150
|
+
announcer.id = "hk-announcer";
|
|
151
151
|
announcer.setAttribute("aria-live", "polite");
|
|
152
152
|
announcer.setAttribute("aria-atomic", "true");
|
|
153
153
|
announcer.style.cssText = "position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap";
|
|
@@ -157,7 +157,7 @@ function navigate(runtime, { base, prefetch: policy, manifest }) {
|
|
|
157
157
|
* Firefox 155 follows a download link's navigate event with a second one that has no downloadRequest.
|
|
158
158
|
*/
|
|
159
159
|
const local = (url, source) => url.origin === location.origin && url.pathname.startsWith(base) &&
|
|
160
|
-
url.pathname.endsWith("/") && !source?.closest("[data-
|
|
160
|
+
url.pathname.endsWith("/") && !source?.closest("[data-hk-reload]") && !source?.hasAttribute("download");
|
|
161
161
|
const payloadURL = (url) => `${base}_markupkit/pages/${url.pathname.slice(base.length)}payload.json${url.search}`;
|
|
162
162
|
// Prefetching never runs anything: payloads are data, and modules and stylesheets are only hinted.
|
|
163
163
|
const hinted = new Set();
|
|
@@ -202,9 +202,9 @@ function navigate(runtime, { base, prefetch: policy, manifest }) {
|
|
|
202
202
|
void chunks.then(map => { for (const src of map.get(url.pathname) ?? [])
|
|
203
203
|
hint("modulepreload", src); });
|
|
204
204
|
};
|
|
205
|
-
/** A link's policy: its own or an ancestor's data-
|
|
205
|
+
/** A link's policy: its own or an ancestor's data-hk-prefetch, else the site's. */
|
|
206
206
|
const mode = (anchor) => {
|
|
207
|
-
const value = anchor.closest("[data-
|
|
207
|
+
const value = anchor.closest("[data-hk-prefetch]")?.getAttribute("data-hk-prefetch");
|
|
208
208
|
return value === "interaction" || value === "visible" || value === "none" ? value : policy;
|
|
209
209
|
};
|
|
210
210
|
// Intent: touch at once; a link that keeps the pointer or focus briefly, so sweeping across a menu
|
|
@@ -391,8 +391,8 @@ function navigate(runtime, { base, prefetch: policy, manifest }) {
|
|
|
391
391
|
// ponytail: WebKit 26.6 sometimes restores no position for an intercepted back or forward
|
|
392
392
|
// navigation, so MarkupKit records where each entry was left. Other scrolling stays native.
|
|
393
393
|
const positions = new Map();
|
|
394
|
-
// While a navigation renders in place: data-
|
|
395
|
-
// data-
|
|
394
|
+
// While a navigation renders in place: data-hk-navigating on <html>, and on a submitting form,
|
|
395
|
+
// data-hk-submitting and aria-busy. Only the latest navigation clears <html>'s.
|
|
396
396
|
let latest = 0;
|
|
397
397
|
navigation.addEventListener("navigate", event => {
|
|
398
398
|
if (navigation.currentEntry !== null)
|
|
@@ -410,17 +410,17 @@ function navigate(runtime, { base, prefetch: policy, manifest }) {
|
|
|
410
410
|
// where the entry was left, and focus resets as after a document load.
|
|
411
411
|
event.intercept({ scroll: "manual", async handler() {
|
|
412
412
|
const id = ++latest;
|
|
413
|
-
document.documentElement.setAttribute("data-
|
|
414
|
-
form?.setAttribute("data-
|
|
413
|
+
document.documentElement.setAttribute("data-hk-navigating", "");
|
|
414
|
+
form?.setAttribute("data-hk-submitting", "");
|
|
415
415
|
form?.setAttribute("aria-busy", "true");
|
|
416
416
|
try {
|
|
417
417
|
await visit(url, event, form);
|
|
418
418
|
}
|
|
419
419
|
finally {
|
|
420
|
-
form?.removeAttribute("data-
|
|
420
|
+
form?.removeAttribute("data-hk-submitting");
|
|
421
421
|
form?.removeAttribute("aria-busy");
|
|
422
422
|
if (id === latest)
|
|
423
|
-
document.documentElement.removeAttribute("data-
|
|
423
|
+
document.documentElement.removeAttribute("data-hk-navigating");
|
|
424
424
|
}
|
|
425
425
|
} });
|
|
426
426
|
});
|
package/dist/resource.d.ts
CHANGED
|
@@ -2,14 +2,14 @@ import { type ComponentDefinition } from "@nextwebwg/html-next";
|
|
|
2
2
|
interface ComponentMetadata {
|
|
3
3
|
readonly layout?: string;
|
|
4
4
|
readonly label?: string;
|
|
5
|
-
/**
|
|
5
|
+
/** hk:order, the page's place among its neighbours, as a number-and-dot prefix gives it. */
|
|
6
6
|
readonly order?: string;
|
|
7
7
|
readonly hidden?: boolean;
|
|
8
|
-
/**
|
|
8
|
+
/** hk:render: "server" renders the page on each request; "prerender", the default, at build time. */
|
|
9
9
|
readonly render?: "prerender" | "server";
|
|
10
10
|
/** Extra route patterns for a page, relative to its page directory's prefix. */
|
|
11
11
|
readonly aliases: readonly string[];
|
|
12
|
-
/**
|
|
12
|
+
/** hk:redirect-from: old route paths, relative to its page directory's prefix, that redirect to the page. */
|
|
13
13
|
readonly redirectsFrom: readonly string[];
|
|
14
14
|
/** The text of a <title> without expressions. */
|
|
15
15
|
readonly title?: string;
|
|
@@ -23,7 +23,7 @@ export declare function applicationResource(source: string, file: string): {
|
|
|
23
23
|
};
|
|
24
24
|
export declare function pageDefinition(resource: ReturnType<typeof applicationResource>, file: string): ComponentDefinition;
|
|
25
25
|
/**
|
|
26
|
-
* A definition without its
|
|
26
|
+
* A definition without its hk: settings, which MarkupKit reads as configuration, so the runtime never
|
|
27
27
|
* adds them to the document head.
|
|
28
28
|
*/
|
|
29
29
|
export declare function withoutSettings(definition: ComponentDefinition): ComponentDefinition;
|
package/dist/resource.js
CHANGED
|
@@ -3,8 +3,8 @@ import { parseFragment } from "parse5";
|
|
|
3
3
|
import { MarkupKitError } from "./config.js";
|
|
4
4
|
// MarkupKit metadata was spelled htmlkit:*; name the new spelling rather than ignore it.
|
|
5
5
|
function renamed(name, file) {
|
|
6
|
-
if (name?.startsWith("htmlkit:")
|
|
7
|
-
throw new MarkupKitError(`MarkupKit metadata uses the
|
|
6
|
+
if (name?.startsWith("htmlkit:"))
|
|
7
|
+
throw new MarkupKitError(`MarkupKit metadata uses the hk: prefix; write ${"hk:" + name.slice(name.indexOf(":") + 1)} instead of ${name}.`, file);
|
|
8
8
|
}
|
|
9
9
|
/** Native HTML parsing determines resource nodes; HTML Next owns component syntax. */
|
|
10
10
|
export function applicationResource(source, file) {
|
|
@@ -16,14 +16,14 @@ export function applicationResource(source, file) {
|
|
|
16
16
|
continue;
|
|
17
17
|
const name = node.attrs.find(attribute => attribute.name === "name")?.value;
|
|
18
18
|
renamed(name, file);
|
|
19
|
-
if (node.tagName === "meta" && name === "
|
|
19
|
+
if (node.tagName === "meta" && name === "hk:page") {
|
|
20
20
|
const content = node.attrs.find(attribute => attribute.name === "content")?.value;
|
|
21
21
|
if (!content || configuration.has(name))
|
|
22
22
|
throw new MarkupKitError(`Declare ${name} once with nonempty content.`, file);
|
|
23
23
|
configuration.set(name, content);
|
|
24
24
|
}
|
|
25
25
|
else if (node.tagName !== "link" || !node.attrs.find(attribute => attribute.name === "rel")?.value.toLowerCase().split(/\s+/).includes("component")) {
|
|
26
|
-
throw new MarkupKitError("Page and layout metadata belongs in its <template component>'s <defs>; only
|
|
26
|
+
throw new MarkupKitError("Page and layout metadata belongs in its <template component>'s <defs>; only hk:page, which selects the file's page, is outside it.", file);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
for (const node of fragment.childNodes) {
|
|
@@ -37,14 +37,14 @@ export function applicationResource(source, file) {
|
|
|
37
37
|
const aliases = [], redirectsFrom = [];
|
|
38
38
|
for (const child of children) {
|
|
39
39
|
if ("tagName" in child && ["meta", "title", "link"].includes(child.tagName)) {
|
|
40
|
-
throw new MarkupKitError(`<${child.tagName}> belongs in <${tag}>'s <defs>, where head content and
|
|
40
|
+
throw new MarkupKitError(`<${child.tagName}> belongs in <${tag}>'s <defs>, where head content and hk: settings are declared.`, file);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
const defs = children.find(child => "tagName" in child && child.tagName === "defs");
|
|
44
44
|
for (const child of defs !== undefined && "childNodes" in defs ? defs.childNodes : []) {
|
|
45
45
|
if (!("tagName" in child))
|
|
46
46
|
continue;
|
|
47
|
-
// A static <title> labels the page's folder in navigation trees when
|
|
47
|
+
// A static <title> labels the page's folder in navigation trees when hk:label doesn't.
|
|
48
48
|
if (child.tagName === "title") {
|
|
49
49
|
const text = child.childNodes.map(text => "value" in text ? text.value : "").join("").trim();
|
|
50
50
|
if (text !== "" && !text.includes("{"))
|
|
@@ -55,30 +55,30 @@ export function applicationResource(source, file) {
|
|
|
55
55
|
continue;
|
|
56
56
|
const name = child.attrs.find(attribute => attribute.name === "name")?.value;
|
|
57
57
|
renamed(name, file);
|
|
58
|
-
if (!name?.startsWith("
|
|
58
|
+
if (!name?.startsWith("hk:"))
|
|
59
59
|
continue;
|
|
60
60
|
const content = child.attrs.find(attribute => attribute.name === "content")?.value;
|
|
61
61
|
if (!content)
|
|
62
62
|
throw new MarkupKitError(`Declare ${name} with nonempty content.`, file);
|
|
63
|
-
if (name === "
|
|
63
|
+
if (name === "hk:alias") {
|
|
64
64
|
aliases.push(content);
|
|
65
65
|
continue;
|
|
66
66
|
}
|
|
67
|
-
if (name === "
|
|
67
|
+
if (name === "hk:redirect-from") {
|
|
68
68
|
redirectsFrom.push(content);
|
|
69
69
|
continue;
|
|
70
70
|
}
|
|
71
|
-
const key = { "
|
|
71
|
+
const key = { "hk:layout": "layout", "hk:label": "label", "hk:order": "order", "hk:navigation": "hidden", "hk:render": "render" }[name];
|
|
72
72
|
if (key === undefined)
|
|
73
|
-
throw new MarkupKitError(`Component configuration is
|
|
73
|
+
throw new MarkupKitError(`Component configuration is hk:layout, hk:label, hk:order, hk:navigation, hk:render, hk:alias, or hk:redirect-from; hk:page belongs outside the carrier (${name}).`, file);
|
|
74
74
|
if (configuration[key] !== undefined)
|
|
75
75
|
throw new MarkupKitError(`Declare ${name} once per component.`, file);
|
|
76
76
|
if (key === "hidden" && content !== "hidden")
|
|
77
|
-
throw new MarkupKitError('
|
|
77
|
+
throw new MarkupKitError('hk:navigation accepts only content="hidden".', file);
|
|
78
78
|
if (key === "order" && !/^\d+$/.test(content))
|
|
79
|
-
throw new MarkupKitError("
|
|
79
|
+
throw new MarkupKitError("hk:order needs a whole number, such as content=\"3\".", file);
|
|
80
80
|
if (key === "render" && content !== "prerender" && content !== "server")
|
|
81
|
-
throw new MarkupKitError('
|
|
81
|
+
throw new MarkupKitError('hk:render accepts content="prerender" or content="server".', file);
|
|
82
82
|
if (key === "hidden")
|
|
83
83
|
configuration.hidden = true;
|
|
84
84
|
else if (key === "render")
|
|
@@ -93,9 +93,9 @@ export function applicationResource(source, file) {
|
|
|
93
93
|
return { definitions: parsed.definitions.map(withoutSettings), dependencies: parsed.dependencies, configuration, components };
|
|
94
94
|
}
|
|
95
95
|
export function pageDefinition(resource, file) {
|
|
96
|
-
const selected = resource.configuration.get("
|
|
96
|
+
const selected = resource.configuration.get("hk:page");
|
|
97
97
|
if (selected === undefined && resource.definitions.length !== 1) {
|
|
98
|
-
throw new MarkupKitError("Multiple components require <meta name=\"
|
|
98
|
+
throw new MarkupKitError("Multiple components require <meta name=\"hk:page\" content=\"page-name\">.", file);
|
|
99
99
|
}
|
|
100
100
|
const definition = selected === undefined ? resource.definitions[0] : resource.definitions.find(value => value.contract.tag === selected);
|
|
101
101
|
if (definition === undefined)
|
|
@@ -103,12 +103,12 @@ export function pageDefinition(resource, file) {
|
|
|
103
103
|
return definition;
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
|
-
* A definition without its
|
|
106
|
+
* A definition without its hk: settings, which MarkupKit reads as configuration, so the runtime never
|
|
107
107
|
* adds them to the document head.
|
|
108
108
|
*/
|
|
109
109
|
export function withoutSettings(definition) {
|
|
110
110
|
const head = definition.head?.filter(declaration => declaration.kind !== "meta" ||
|
|
111
|
-
!declaration.attributes.some(attribute => attribute.kind === "literal" && attribute.name === "name" && attribute.value.startsWith("
|
|
111
|
+
!declaration.attributes.some(attribute => attribute.kind === "literal" && attribute.name === "name" && attribute.value.startsWith("hk:")));
|
|
112
112
|
if (head === undefined || head.length === definition.head.length)
|
|
113
113
|
return definition;
|
|
114
114
|
const { head: _head, ...rest } = definition;
|
package/dist/resource.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAA4B,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,aAAa,EAA8B,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkB7C,yFAAyF;AACzF,SAAS,OAAO,CAAC,IAAwB,EAAE,IAAY;IACrD,IAAI,IAAI,EAAE,UAAU,CAAC,UAAU,CAAC
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
|
package/dist/routes.js
CHANGED
|
@@ -260,15 +260,15 @@ export async function discover(root, options) {
|
|
|
260
260
|
const metadata = resource.components.get(pageDefinition(resource, route.component).contract.tag);
|
|
261
261
|
for (const from of metadata.redirectsFrom) {
|
|
262
262
|
if (!/^\/(?:[^/]+\/)*$/.test(from) || !from.slice(1, -1).split("/").filter(Boolean).every(validSegment)) {
|
|
263
|
-
throw new MarkupKitError("
|
|
263
|
+
throw new MarkupKitError("hk:redirect-from needs a route path with leading and trailing slashes.", route.component);
|
|
264
264
|
}
|
|
265
265
|
if (route.params.length > 0)
|
|
266
|
-
throw new MarkupKitError(`
|
|
266
|
+
throw new MarkupKitError(`hk:redirect-from redirects to one URL, and ${route.pattern} has parameters.`, route.component);
|
|
267
267
|
redirects.push({ from: route.prefix + from.slice(1), to: route.pattern, component: route.component });
|
|
268
268
|
}
|
|
269
269
|
for (const alias of metadata.aliases) {
|
|
270
270
|
if (!/^\/(?:[^/]+\/)*$/.test(alias))
|
|
271
|
-
throw new MarkupKitError("
|
|
271
|
+
throw new MarkupKitError("hk:alias needs a route path with leading and trailing slashes.", route.component);
|
|
272
272
|
add({ component: route.component, ...(route.server === undefined ? {} : { server: route.server }), layouts: route.layouts,
|
|
273
273
|
pattern: route.prefix + alias.slice(1), canonical: route.pattern, ...(route.aliasOf === undefined ? {} : { aliasOf: route.aliasOf }) }, route.prefix, undefined, route.directoryLayout, route.folderLayouts);
|
|
274
274
|
}
|
|
@@ -290,10 +290,10 @@ export async function discover(root, options) {
|
|
|
290
290
|
for (const { from, component } of redirects) {
|
|
291
291
|
const page = routes.find(route => !route.error && !route.notFound && route.pattern.toLowerCase() === from.toLowerCase());
|
|
292
292
|
if (page !== undefined)
|
|
293
|
-
throw new MarkupKitError(`
|
|
293
|
+
throw new MarkupKitError(`hk:redirect-from ${from} is ${page.component === component ? "this page's own URL" : `the URL of ${page.component}`}.`, component);
|
|
294
294
|
const other = redirected.get(from);
|
|
295
295
|
if (other !== undefined)
|
|
296
|
-
throw new MarkupKitError(`
|
|
296
|
+
throw new MarkupKitError(`hk:redirect-from ${from} is declared by ${other} and ${component}.`, component);
|
|
297
297
|
redirected.set(from, component);
|
|
298
298
|
}
|
|
299
299
|
const pageNames = new Map();
|
|
@@ -370,22 +370,22 @@ export async function discover(root, options) {
|
|
|
370
370
|
// The selected layout is outermost and folder layouts nest inside it; none drops them all.
|
|
371
371
|
if (selected !== false && selected !== "none")
|
|
372
372
|
layouts = [...layouts, ...folderLayouts];
|
|
373
|
-
//
|
|
374
|
-
//
|
|
373
|
+
// hk:order takes the place of the number-and-dot prefix on the page's own last segment.
|
|
374
|
+
// hk:order takes the place of the number-and-dot prefix on the page's own last segment; on a
|
|
375
375
|
// folder's index page, that segment is the folder's, so it orders the folder (below).
|
|
376
376
|
let order = route.order;
|
|
377
377
|
if (metadata.order !== undefined && route.canonical === undefined && !isNotFound && !isError) {
|
|
378
378
|
if (route.segments.length === 0)
|
|
379
|
-
throw new MarkupKitError("
|
|
379
|
+
throw new MarkupKitError("hk:order on the site's home page has no folder to order; it orders a page among its folder's pages.", route.component);
|
|
380
380
|
order = [...route.segments.slice(0, -1).map((_, i) => route.order?.[i] ?? null), metadata.order];
|
|
381
381
|
if (index)
|
|
382
382
|
folderOrders.push({ segments: route.segments, rank: metadata.order });
|
|
383
383
|
}
|
|
384
|
-
// A static <title> labels the page's folder in navigation trees when
|
|
384
|
+
// A static <title> labels the page's folder in navigation trees when hk:label doesn't.
|
|
385
385
|
const title = metadata.title;
|
|
386
|
-
// The not-found page is prerendered as 404.html, and the error page renders as a build would; neither takes
|
|
386
|
+
// The not-found page is prerendered as 404.html, and the error page renders as a build would; neither takes hk:render.
|
|
387
387
|
if ((isNotFound || isError) && metadata.render !== undefined)
|
|
388
|
-
throw new MarkupKitError(`The ${isNotFound ? "not-found" : "error"} page takes no
|
|
388
|
+
throw new MarkupKitError(`The ${isNotFound ? "not-found" : "error"} page takes no hk:render.`, route.component);
|
|
389
389
|
const discoveredRoute = { ...route, layouts, pageName, ...(order === undefined ? {} : { order }), ...(metadata.label === undefined ? {} : { label: metadata.label }),
|
|
390
390
|
...(title ? { title } : {}), ...(metadata.hidden ? { hidden: true } : {}), ...(metadata.render === "server" ? { render: "server" } : {}) };
|
|
391
391
|
if (isNotFound)
|
|
@@ -395,7 +395,7 @@ export async function discover(root, options) {
|
|
|
395
395
|
else
|
|
396
396
|
discovered.push(discoveredRoute);
|
|
397
397
|
}
|
|
398
|
-
// Every page in a folder whose index page sets
|
|
398
|
+
// Every page in a folder whose index page sets hk:order takes that rank for the folder's segment.
|
|
399
399
|
for (const { segments, rank } of folderOrders) {
|
|
400
400
|
for (const [i, route] of discovered.entries()) {
|
|
401
401
|
if (route.canonical !== undefined || route.segments.length < segments.length || segments.some((segment, j) => route.segments[j] !== segment))
|
|
@@ -513,14 +513,14 @@ export async function discoverApplication(input = {}) {
|
|
|
513
513
|
const option = redirectTable(config.base, options.redirects);
|
|
514
514
|
const discovery = await discover(config.root, { ...options, base: config.base });
|
|
515
515
|
const { routes, redirects: declared } = discovery;
|
|
516
|
-
// Pages'
|
|
516
|
+
// Pages' hk:redirect-from entries join the redirects option's, rediscovered with the pages.
|
|
517
517
|
const redirects = new Map(option);
|
|
518
518
|
// Each page redirect's deployment path, for the declaring page's diagnostics.
|
|
519
519
|
const pageRedirects = new Map();
|
|
520
520
|
for (const { from, to, component } of declared) {
|
|
521
521
|
const path = new URL(config.base + from.slice(1), "http://localhost").pathname;
|
|
522
522
|
if (option.has(path))
|
|
523
|
-
throw new MarkupKitError(`
|
|
523
|
+
throw new MarkupKitError(`hk:redirect-from ${from} is also a key of the redirects option.`, component);
|
|
524
524
|
redirects.set(path, config.base + to.slice(1));
|
|
525
525
|
pageRedirects.set(path, { from, component });
|
|
526
526
|
}
|
package/dist/server.js
CHANGED
|
@@ -122,7 +122,7 @@ export function createNavigation(base, routes, concrete) {
|
|
|
122
122
|
const finish = (draft, depth) => {
|
|
123
123
|
const children = draft.children.map(child => finish(child, depth + 1));
|
|
124
124
|
const { item, route } = draft;
|
|
125
|
-
// A folder's index page labels the folder, by
|
|
125
|
+
// A folder's index page labels the folder, by hk:label, then its <title>.
|
|
126
126
|
const label = item === undefined ? draft.label : children.length > 0 && route.label === undefined ? route.title ?? draft.label : item.label;
|
|
127
127
|
const current = item?.current ?? "false";
|
|
128
128
|
return Object.freeze({ label, ...(item === undefined ? {} : { href: item.href, pageName: item.pageName }), current,
|
|
@@ -177,7 +177,7 @@ function invocationAttributes(definition, result, deferred) {
|
|
|
177
177
|
}
|
|
178
178
|
/** The browser builds the same invocation from a page payload (client.ts). */
|
|
179
179
|
function invocation(tag, attributes, index, child, nested) {
|
|
180
|
-
return `<${tag}${Object.hasOwn(attributes, "id") ? "" : ` id="
|
|
180
|
+
return `<${tag}${Object.hasOwn(attributes, "id") ? "" : ` id="hk-layer-${index}"`} data-hk-layer="${index}"${nested ? ' slot="page"' : ""}${Object.entries(attributes).map(([name, value]) => ` ${name}="${escapeHTML(value)}"`).join("")}>${child}</${tag}>`;
|
|
181
181
|
}
|
|
182
182
|
/** The request's cookies by name; the first of a repeated name wins, as the most specific path comes first. */
|
|
183
183
|
function parseCookies(header) {
|
|
@@ -228,7 +228,7 @@ export async function renderRoute(input) {
|
|
|
228
228
|
const current = route.canonical === undefined ? url.pathname : at(route.canonical);
|
|
229
229
|
const contextOf = (layer) => {
|
|
230
230
|
const prerendering = (name) => {
|
|
231
|
-
throw new MarkupKitError(`${name} is unavailable while prerendering; <meta name="
|
|
231
|
+
throw new MarkupKitError(`${name} is unavailable while prerendering; <meta name="hk:render" content="server"> renders the page on each request.`, layer.origin);
|
|
232
232
|
};
|
|
233
233
|
return { phase: request === undefined ? "prerender" : "request", url: new URL(url), base, params, parent,
|
|
234
234
|
propNames: Object.freeze(Object.keys(layer.definition.contract.props)),
|
|
@@ -310,7 +310,7 @@ export async function renderRoute(input) {
|
|
|
310
310
|
const names = input.providers?.[layer.definition.contract.tag] === undefined && !client ? undefined : [];
|
|
311
311
|
attributes.push(invocationAttributes(layer.definition, result, names));
|
|
312
312
|
if (!client && names !== undefined && names.length > 0)
|
|
313
|
-
required.push({ tag: layer.definition.contract.tag, selector: `[data-
|
|
313
|
+
required.push({ tag: layer.definition.contract.tag, selector: `[data-hk-layer="${index}"]`, names });
|
|
314
314
|
}
|
|
315
315
|
const state = {};
|
|
316
316
|
const props = {};
|
|
@@ -319,7 +319,7 @@ export async function renderRoute(input) {
|
|
|
319
319
|
...(results[i].state === undefined ? {} : { state: results[i].state }) }));
|
|
320
320
|
let body = "";
|
|
321
321
|
for (let i = layers.length - 1; i >= 0; i--) {
|
|
322
|
-
const selector = `[data-
|
|
322
|
+
const selector = `[data-hk-layer="${i}"]`;
|
|
323
323
|
const layer = rendered[i];
|
|
324
324
|
if (layer.state !== undefined)
|
|
325
325
|
state[selector] = layer.state;
|
|
@@ -462,7 +462,7 @@ export function pageAction(module, url) {
|
|
|
462
462
|
}
|
|
463
463
|
/** The build error for actions on a page that prerenders. */
|
|
464
464
|
export function prerenderedActions(pattern, origin) {
|
|
465
|
-
return new MarkupKitError(`The page ${pattern} has actions, which answer requests; <meta name="
|
|
465
|
+
return new MarkupKitError(`The page ${pattern} has actions, which answer requests; <meta name="hk:render" content="server"> renders it on each request.`, origin);
|
|
466
466
|
}
|
|
467
467
|
async function answerEndpoint(app, endpoint, params, request) {
|
|
468
468
|
const module = await app.endpoint(endpoint);
|
package/dist/types.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface ApplicationOptions {
|
|
|
17
17
|
/** Named layout, or a layout a plugin supplies; default.html is automatic when present. false disables it. */
|
|
18
18
|
readonly layout?: string | false | RouteLayer;
|
|
19
19
|
/**
|
|
20
|
-
* Named layouts beside app/layouts, such as { home: { component, server: { load } } }.
|
|
20
|
+
* Named layouts beside app/layouts, such as { home: { component, server: { load } } }. hk:layout,
|
|
21
21
|
* layout, layoutDefaults, and a page directory's layout name them. A name resolves to an entry here,
|
|
22
22
|
* then app/layouts/<name>.html, then a plugin's layouts entry, earliest plugin first.
|
|
23
23
|
*/
|
|
@@ -27,7 +27,7 @@ export interface ApplicationOptions {
|
|
|
27
27
|
readonly plugins?: readonly MarkupKitPlugin[];
|
|
28
28
|
/**
|
|
29
29
|
* Global stylesheets on every page, relative to the root or starting with @/. Unlike component
|
|
30
|
-
* styles they are not scoped, so they can style built-in components such as <
|
|
30
|
+
* styles they are not scoped, so they can style built-in components such as <hk-nav>.
|
|
31
31
|
*/
|
|
32
32
|
readonly css?: readonly string[];
|
|
33
33
|
/** A classic script inlined with app/head.js, before it, so it runs before first paint; plugins may set it. */
|
|
@@ -35,7 +35,7 @@ export interface ApplicationOptions {
|
|
|
35
35
|
/**
|
|
36
36
|
* What links prefetch before a click: "interaction" (default) fetches a page's payload and module
|
|
37
37
|
* when its link is hovered, focused, or touched; "visible" also does so for links on screen; "none"
|
|
38
|
-
* prefetches nothing. A link or its ancestor overrides it with data-
|
|
38
|
+
* prefetches nothing. A link or its ancestor overrides it with data-hk-prefetch.
|
|
39
39
|
*/
|
|
40
40
|
readonly prefetch?: PrefetchPolicy;
|
|
41
41
|
/**
|
|
@@ -219,7 +219,7 @@ export interface LoaderResult {
|
|
|
219
219
|
readonly head?: PageHead;
|
|
220
220
|
}
|
|
221
221
|
export interface LoadContext {
|
|
222
|
-
/** "request" for a page with
|
|
222
|
+
/** "request" for a page with hk:render="server", which renders on each request; "prerender" at build time. */
|
|
223
223
|
readonly phase: "prerender" | "request";
|
|
224
224
|
/** The page URL: the request's, query included, or for prerendering, the page path under the configured origin. */
|
|
225
225
|
readonly url: URL;
|
|
@@ -233,11 +233,11 @@ export interface LoadContext {
|
|
|
233
233
|
readonly signal: AbortSignal;
|
|
234
234
|
/** Ordered concrete routes. from is an application-relative directory prefix. */
|
|
235
235
|
readonly navigation: (options?: NavigationQuery) => Promise<readonly NavigationItem[]>;
|
|
236
|
-
/** The same pages as navigation(), nested by folder and (group) folder, for <
|
|
236
|
+
/** The same pages as navigation(), nested by folder and (group) folder, for <hk-nav>. */
|
|
237
237
|
readonly navigationTree: (options?: NavigationQuery) => Promise<readonly NavigationNode[]>;
|
|
238
|
-
/** Each existing page from `from` (default /) down to the current page, for <
|
|
238
|
+
/** Each existing page from `from` (default /) down to the current page, for <hk-breadcrumbs>. */
|
|
239
239
|
readonly breadcrumbs: (options?: NavigationQuery) => Promise<readonly NavigationItem[]>;
|
|
240
|
-
/** The pages before and after the current one in navigation order, for <
|
|
240
|
+
/** The pages before and after the current one in navigation order, for <hk-pager>. */
|
|
241
241
|
readonly pager: (options?: NavigationQuery) => Promise<PagerLinks>;
|
|
242
242
|
/**
|
|
243
243
|
* Stop loading and redirect to an application path or an http(s) URL, with status 303 unless
|
|
@@ -323,13 +323,13 @@ export interface ApplicationRoute extends RouteLayer {
|
|
|
323
323
|
readonly segments: readonly string[];
|
|
324
324
|
readonly params: readonly string[];
|
|
325
325
|
readonly layouts: readonly RouteLayer[];
|
|
326
|
-
/** From the page's
|
|
326
|
+
/** From the page's hk:label metadata; navigation otherwise labels the last URL segment. */
|
|
327
327
|
readonly label?: string;
|
|
328
|
-
/** The page's static <title>, which labels its folder in a navigation tree when
|
|
328
|
+
/** The page's static <title>, which labels its folder in a navigation tree when hk:label is absent. */
|
|
329
329
|
readonly title?: string;
|
|
330
|
-
/**
|
|
330
|
+
/** hk:navigation="hidden" metadata leaves the page out of navigation. */
|
|
331
331
|
readonly hidden?: boolean;
|
|
332
|
-
/** For an
|
|
332
|
+
/** For an hk:alias route, the pattern of the page's own route, which navigation marks current. */
|
|
333
333
|
readonly canonical?: string;
|
|
334
334
|
/** For a page in an alias page directory, the pattern of the same page under its directory's own prefix, which its canonical link names. */
|
|
335
335
|
readonly aliasOf?: string;
|
|
@@ -337,7 +337,7 @@ export interface ApplicationRoute extends RouteLayer {
|
|
|
337
337
|
readonly order?: readonly (string | null)[];
|
|
338
338
|
/** The (group) folders the page is in, outermost first. */
|
|
339
339
|
readonly groups?: readonly RouteGroup[];
|
|
340
|
-
/** From the page's
|
|
340
|
+
/** From the page's hk:render metadata: "server" renders it on each request instead of at build time. */
|
|
341
341
|
readonly render?: "server";
|
|
342
342
|
}
|
|
343
343
|
/** A (group) folder: it orders and labels its pages and adds no URL segment. */
|
|
@@ -373,7 +373,7 @@ export interface NavigationItem {
|
|
|
373
373
|
* an index page is that page's node, with the folder's pages as children.
|
|
374
374
|
*/
|
|
375
375
|
export interface NavigationNode {
|
|
376
|
-
/** A page's label; a folder's index page's
|
|
376
|
+
/** A page's label; a folder's index page's hk:label, then its <title>, then the folder name; a group's parentheses. */
|
|
377
377
|
readonly label: string;
|
|
378
378
|
/** The page URL, for a page or a folder with an index page. */
|
|
379
379
|
readonly href?: string;
|
|
@@ -383,7 +383,7 @@ export interface NavigationNode {
|
|
|
383
383
|
readonly open: boolean;
|
|
384
384
|
/** The nesting level, 0 at the top of the tree. */
|
|
385
385
|
readonly depth: number;
|
|
386
|
-
/** The number-and-dot or
|
|
386
|
+
/** The number-and-dot or hk:order rank, or null when the node has none. */
|
|
387
387
|
readonly order: string | null;
|
|
388
388
|
readonly children: readonly NavigationNode[];
|
|
389
389
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threadlabs/markupkit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Build applications with HTML, CSS, and JavaScript, powered by HTML Next.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,24 +27,6 @@
|
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=22.22.2 <23 || >=24.15 <25"
|
|
29
29
|
},
|
|
30
|
-
"scripts": {
|
|
31
|
-
"build": "pnpm clean && tsc -p tsconfig.build.json",
|
|
32
|
-
"clean": "node --input-type=module --eval \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true });\"",
|
|
33
|
-
"test": "pnpm build && vitest run --exclude tests/package.test.ts",
|
|
34
|
-
"typecheck": "pnpm build && tsc -p tsconfig.json",
|
|
35
|
-
"prepack": "pnpm build",
|
|
36
|
-
"build:docs-proof": "tsx examples/docs/build.ts",
|
|
37
|
-
"measure:delivery": "tsx scripts/measure-delivery.ts",
|
|
38
|
-
"measure:navigation": "tsx scripts/measure-navigation.ts",
|
|
39
|
-
"measure:server": "tsx scripts/measure-server.ts",
|
|
40
|
-
"lint": "oxlint --deny-warnings .",
|
|
41
|
-
"test:package": "vitest run tests/package.test.ts",
|
|
42
|
-
"test:browser": "pnpm build && vitest run --config vitest.browser.config.ts",
|
|
43
|
-
"verify:inner": "pnpm lint && pnpm typecheck && pnpm test",
|
|
44
|
-
"verify:pr": "pnpm verify:inner && pnpm build && pnpm test:package && pnpm build:site",
|
|
45
|
-
"verify:release": "pnpm verify:pr",
|
|
46
|
-
"build:site": "node dist/cli.js build website"
|
|
47
|
-
},
|
|
48
30
|
"dependencies": {
|
|
49
31
|
"@nextwebwg/html-next": "1.0.0-alpha.69",
|
|
50
32
|
"typescript-compiler": "npm:@typescript/typescript6@6.0.2",
|
|
@@ -59,8 +41,8 @@
|
|
|
59
41
|
"marked": "18.1.1",
|
|
60
42
|
"tsx": "4.23.15",
|
|
61
43
|
"oxlint": "1.81.0",
|
|
62
|
-
"markupress": "0.
|
|
63
|
-
"@threadlabs/looma": "0.
|
|
44
|
+
"markupress": ">=0.12.0 <1",
|
|
45
|
+
"@threadlabs/looma": "0.44.0"
|
|
64
46
|
},
|
|
65
47
|
"publishConfig": {
|
|
66
48
|
"access": "public",
|
|
@@ -74,9 +56,25 @@
|
|
|
74
56
|
"bin": {
|
|
75
57
|
"markupkit": "./dist/cli.js"
|
|
76
58
|
},
|
|
77
|
-
"packageManager": "pnpm@12.9.1",
|
|
78
59
|
"homepage": "https://threadlabs-studio.github.io/markupkit/",
|
|
79
60
|
"bugs": {
|
|
80
61
|
"url": "https://github.com/threadlabs-studio/markupkit/issues"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "pnpm clean && tsc -p tsconfig.build.json",
|
|
65
|
+
"clean": "node --input-type=module --eval \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true });\"",
|
|
66
|
+
"test": "pnpm build && vitest run --exclude tests/package.test.ts",
|
|
67
|
+
"typecheck": "pnpm build && tsc -p tsconfig.json",
|
|
68
|
+
"build:docs-proof": "tsx examples/docs/build.ts",
|
|
69
|
+
"measure:delivery": "tsx scripts/measure-delivery.ts",
|
|
70
|
+
"measure:navigation": "tsx scripts/measure-navigation.ts",
|
|
71
|
+
"measure:server": "tsx scripts/measure-server.ts",
|
|
72
|
+
"lint": "oxlint --deny-warnings .",
|
|
73
|
+
"test:package": "vitest run tests/package.test.ts",
|
|
74
|
+
"test:browser": "pnpm build && vitest run --config vitest.browser.config.ts",
|
|
75
|
+
"verify:inner": "pnpm lint && pnpm typecheck && pnpm test",
|
|
76
|
+
"verify:pr": "pnpm verify:inner && pnpm build && pnpm test:package && pnpm build:site",
|
|
77
|
+
"verify:release": "pnpm verify:pr",
|
|
78
|
+
"build:site": "markupress build --root website"
|
|
81
79
|
}
|
|
82
|
-
}
|
|
80
|
+
}
|