@heroiclands/package-build 23.13.0 → 24.0.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/CHANGELOG.md +50 -0
- package/README.md +2 -1
- package/assets/preview/gitea.ini +12 -7
- package/assets/styles/content-base.css +150 -1
- package/bin/content-commands.mjs +356 -210
- package/bin/package-build.mjs +3 -2
- package/ci/check-banners.mjs +7 -7
- package/config.mjs +21 -6
- package/content-config.mjs +199 -80
- package/docs/api.md +93 -72
- package/docs/authoring/links-and-markup.md +316 -78
- package/docs/commands.md +248 -80
- package/docs/configuration.md +210 -102
- package/docs/content-format.md +2 -2
- package/docs/diagnostics.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/guides/icons.md +1 -1
- package/docs/guides/module-project.md +5 -8
- package/docs/guides/release-and-deploy.md +2 -2
- package/docs/guides/site.md +155 -23
- package/docs/guides/system-project.md +1 -1
- package/docs/guides/writing-in-invented-scripts.md +1247 -0
- package/docs/index.md +1 -0
- package/docs/project-setup.md +28 -30
- package/docs/reference/format-details.md +589 -308
- package/docs/reference/note-types.md +40 -10
- package/docs/reference/theme-pages.md +127 -108
- package/docs/source-preview.md +15 -1
- package/engine/asset-shipping.mjs +22 -10
- package/engine/base-compiler.mjs +62 -104
- package/engine/changelog-lint.mjs +1 -1
- package/engine/content-address.mjs +1 -1
- package/engine/content-alerts.mjs +8 -4
- package/engine/content-blocks.mjs +26 -11
- package/engine/content-embeds.mjs +1 -1
- package/engine/content-expand.mjs +376 -0
- package/engine/content-figures.mjs +13 -6
- package/engine/content-format.yaml +8 -0
- package/engine/content-glyphs.mjs +213 -0
- package/engine/content-images.mjs +6 -7
- package/engine/content-links.mjs +3 -3
- package/engine/content-lint.mjs +1 -1
- package/engine/content-render.mjs +453 -0
- package/engine/content-scripts.mjs +326 -27
- package/engine/content-spans.mjs +4 -1
- package/engine/content-styles.mjs +91 -0
- package/engine/content-symbols.mjs +186 -0
- package/engine/content-verse.mjs +6 -18
- package/engine/core-report.mjs +169 -0
- package/engine/core-vocabulary.mjs +208 -0
- package/engine/dictionary-html.mjs +119 -0
- package/engine/field-reference.mjs +1 -1
- package/engine/frontmatter-lint.mjs +1 -1
- package/engine/heading-attributes.mjs +89 -0
- package/engine/helpers.mjs +61 -101
- package/engine/homepage.mjs +17 -29
- package/engine/html-minify.mjs +70 -0
- package/engine/index.mjs +8 -5
- package/engine/language-rows.mjs +8 -2
- package/engine/language-terms.mjs +115 -14
- package/engine/language-words.mjs +12 -0
- package/engine/logographic.mjs +255 -0
- package/engine/map-lexicon.mjs +221 -25
- package/engine/map-places.mjs +5 -4
- package/engine/markdown-expressions.mjs +0 -2
- package/engine/markdownlint-run.mjs +9 -3
- package/engine/markup-validation.mjs +21 -0
- package/engine/metre-declaration.mjs +134 -0
- package/engine/note-matter.mjs +2 -2
- package/engine/note-vocabulary.mjs +187 -3
- package/engine/pdf-build.mjs +68 -133
- package/engine/pdf-render.mjs +102 -44
- package/engine/post-fields.mjs +157 -0
- package/engine/preview-index.mjs +130 -0
- package/engine/preview-source.mjs +450 -0
- package/engine/project-init.mjs +5 -12
- package/engine/prose-config.mjs +3 -3
- package/engine/run-words.mjs +3 -2
- package/engine/script-emphasis-terms.mjs +38 -0
- package/engine/script-emphasis.mjs +295 -0
- package/engine/script-font-delivery.mjs +4 -4
- package/engine/site-build.mjs +265 -313
- package/engine/site-config.mjs +26 -238
- package/engine/site-files.mjs +175 -0
- package/engine/site-index.mjs +9 -4
- package/engine/site-preview.mjs +50 -523
- package/engine/site-reach.mjs +213 -0
- package/engine/site-render.mjs +668 -0
- package/engine/site-root.mjs +90 -39
- package/engine/site-search.mjs +161 -0
- package/engine/site-serve.mjs +274 -0
- package/engine/site-theme-banner.mjs +154 -0
- package/engine/site-theme-urls.mjs +125 -0
- package/engine/site-theme.mjs +939 -0
- package/engine/sql-tables.mjs +9 -6
- package/engine/text-maps.mjs +57 -10
- package/engine/typographer.mjs +248 -0
- package/engine/verse-metre.mjs +357 -0
- package/engine/wikilink-syntax.mjs +29 -47
- package/engine/wikilinks.mjs +758 -279
- package/engine/word-forms.mjs +39 -24
- package/engine/word-rows.mjs +12 -2
- package/engine/word-terms.mjs +29 -4
- package/manifest.mjs +3 -4
- package/package.json +10 -7
- package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
- package/site-theme/layouts/base.hbs +31 -0
- package/site-theme/layouts/blog-list.hbs +30 -0
- package/site-theme/layouts/blog-post.hbs +40 -0
- package/site-theme/layouts/home.hbs +35 -0
- package/site-theme/layouts/list.hbs +20 -0
- package/site-theme/layouts/not-found.hbs +31 -0
- package/site-theme/layouts/page.hbs +7 -0
- package/site-theme/partials/copy-buttons.hbs +37 -0
- package/site-theme/partials/draft-notice.hbs +5 -0
- package/site-theme/partials/exturl.hbs +2 -0
- package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
- package/site-theme/partials/header.hbs +44 -0
- package/site-theme/partials/hero-banner.hbs +11 -0
- package/site-theme/partials/infobox-entry.hbs +2 -0
- package/site-theme/partials/infobox-section.hbs +31 -0
- package/site-theme/partials/infobox-value.hbs +2 -0
- package/site-theme/partials/infobox.hbs +18 -0
- package/site-theme/partials/list-members.hbs +9 -0
- package/site-theme/partials/nav-links.hbs +5 -0
- package/site-theme/partials/page-body.hbs +48 -0
- package/site-theme/partials/page-list.hbs +14 -0
- package/site-theme/partials/related.hbs +24 -0
- package/site-theme/partials/search-meta.hbs +6 -0
- package/site-theme/partials/search-names.hbs +2 -0
- package/site-theme/partials/search.hbs +52 -0
- package/site-theme/partials/tags.hbs +4 -0
- package/site-theme/partials/toc.hbs +7 -0
- package/{hugo-theme → site-theme}/static/css/style.css +14 -0
- package/stage.mjs +1 -2
- package/types/config.d.mts +8 -4
- package/types/content-config.d.mts +15 -15
- package/types/engine/asset-shipping.d.mts +5 -2
- package/types/engine/base-compiler.d.mts +20 -2
- package/types/engine/content-address.d.mts +1 -1
- package/types/engine/content-alerts.d.mts +8 -2
- package/types/engine/content-blocks.d.mts +11 -4
- package/types/engine/content-expand.d.mts +288 -0
- package/types/engine/content-figures.d.mts +11 -3
- package/types/engine/content-glyphs.d.mts +85 -0
- package/types/engine/content-images.d.mts +6 -7
- package/types/engine/content-links.d.mts +1 -1
- package/types/engine/content-render.d.mts +199 -0
- package/types/engine/content-scripts.d.mts +62 -0
- package/types/engine/content-spans.d.mts +1 -1
- package/types/engine/content-styles.d.mts +33 -0
- package/types/engine/content-symbols.d.mts +35 -0
- package/types/engine/core-report.d.mts +45 -0
- package/types/engine/core-vocabulary.d.mts +53 -0
- package/types/engine/dictionary-html.d.mts +8 -0
- package/types/engine/field-reference.d.mts +1 -1
- package/types/engine/heading-attributes.d.mts +20 -0
- package/types/engine/helpers.d.mts +3 -10
- package/types/engine/homepage.d.mts +11 -24
- package/types/engine/html-minify.d.mts +9 -0
- package/types/engine/index.d.mts +2 -1
- package/types/engine/language-terms.d.mts +24 -2
- package/types/engine/logographic.d.mts +45 -0
- package/types/engine/map-lexicon.d.mts +48 -8
- package/types/engine/map-places.d.mts +8 -0
- package/types/engine/markdown-expressions.d.mts +0 -2
- package/types/engine/metre-declaration.d.mts +39 -0
- package/types/engine/pdf-render.d.mts +6 -2
- package/types/engine/post-fields.d.mts +44 -0
- package/types/engine/preview-index.d.mts +72 -0
- package/types/engine/preview-source.d.mts +51 -0
- package/types/engine/script-emphasis-terms.d.mts +21 -0
- package/types/engine/script-emphasis.d.mts +84 -0
- package/types/engine/script-font-delivery.d.mts +4 -4
- package/types/engine/site-build.d.mts +71 -113
- package/types/engine/site-config.d.mts +14 -134
- package/types/engine/site-files.d.mts +73 -0
- package/types/engine/site-index.d.mts +11 -3
- package/types/engine/site-preview.d.mts +15 -19
- package/types/engine/site-reach.d.mts +76 -0
- package/types/engine/site-render.d.mts +173 -0
- package/types/engine/site-root.d.mts +14 -4
- package/types/engine/site-search.d.mts +54 -0
- package/types/engine/site-serve.d.mts +44 -0
- package/types/engine/site-theme-banner.d.mts +51 -0
- package/types/engine/site-theme-urls.d.mts +60 -0
- package/types/engine/site-theme.d.mts +127 -0
- package/types/engine/text-maps.d.mts +17 -3
- package/types/engine/typographer.d.mts +11 -0
- package/types/engine/verse-metre.d.mts +131 -0
- package/types/engine/wikilink-syntax.d.mts +30 -48
- package/types/engine/wikilinks.d.mts +330 -14
- package/types/engine/word-forms.d.mts +3 -2
- package/types/manifest.d.mts +3 -4
- package/types/stage.d.mts +1 -2
- package/types/vendor/markdownlint.d.mts +8 -0
- package/vendor/markdownlint.mjs +20388 -0
- package/engine/web-wikilinks.mjs +0 -594
- package/hugo-theme/layouts/404.html +0 -85
- package/hugo-theme/layouts/_default/baseof.html +0 -67
- package/hugo-theme/layouts/_default/list.html +0 -139
- package/hugo-theme/layouts/_default/single.html +0 -4
- package/hugo-theme/layouts/blog/list.html +0 -54
- package/hugo-theme/layouts/blog-post/single.html +0 -36
- package/hugo-theme/layouts/index.html +0 -75
- package/hugo-theme/layouts/partials/banner-url.html +0 -141
- package/hugo-theme/layouts/partials/cdn-url.html +0 -36
- package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
- package/hugo-theme/layouts/partials/draft-notice.html +0 -41
- package/hugo-theme/layouts/partials/draft-state.html +0 -26
- package/hugo-theme/layouts/partials/header.html +0 -35
- package/hugo-theme/layouts/partials/hero-banner.html +0 -85
- package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
- package/hugo-theme/layouts/partials/infobox/items.html +0 -26
- package/hugo-theme/layouts/partials/infobox/section.html +0 -56
- package/hugo-theme/layouts/partials/infobox/value.html +0 -43
- package/hugo-theme/layouts/partials/infobox.html +0 -57
- package/hugo-theme/layouts/partials/page-body.html +0 -152
- package/hugo-theme/layouts/partials/related.html +0 -138
- package/hugo-theme/layouts/partials/search.html +0 -84
- package/hugo-theme/layouts/partials/site-url.html +0 -32
- package/hugo-theme/layouts/partials/tags.html +0 -30
- package/hugo-theme/layouts/partials/toc.html +0 -28
- package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
- package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
- package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
- package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
- package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
- package/hugo-theme/theme.toml +0 -11
- package/types/engine/web-wikilinks.d.mts +0 -107
- /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
- /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
- /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
- /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
- /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
- /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **A page's banner image.** {@link bannerUrl} turns a note's banner fields into
|
|
16
|
+
* the address of the hero image, or into `""` when the page has none.
|
|
17
|
+
*
|
|
18
|
+
* Resolution, in order:
|
|
19
|
+
*
|
|
20
|
+
* 1. `data.banner` — a note's own hero image, which the content build resolved
|
|
21
|
+
* from an asset address. It is used as given; a blank one declines an image.
|
|
22
|
+
* 2. `banner` — a hand-authored page's value. `none` or `false` declines an
|
|
23
|
+
* image, a full URL is used as written, and anything else is a fragment
|
|
24
|
+
* under `images/`.
|
|
25
|
+
* 3. `<dir>/<name>.webp` — the default for the page's kind: the banner
|
|
26
|
+
* `banner-kinds.yaml` maps its `type` and `subType` to, or its `category`
|
|
27
|
+
* for the types the mapping lists as taking theirs from there.
|
|
28
|
+
* 4. `<dir>/<fallback>` — whenever the name step 2 or 3 picked is not in the
|
|
29
|
+
* declared inventory of `banners.yaml`. An asset host cannot be asked
|
|
30
|
+
* whether a name exists, so the inventory stands in for the test, and each
|
|
31
|
+
* name that misses is reported once.
|
|
32
|
+
*
|
|
33
|
+
* The inventory is checked only for a relative path directly inside the banner
|
|
34
|
+
* directory; an absolute URL, a step 1 address and a fragment elsewhere under
|
|
35
|
+
* `images/` pass through.
|
|
36
|
+
*
|
|
37
|
+
* @module
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import fs from "node:fs";
|
|
41
|
+
import path from "node:path";
|
|
42
|
+
import { fileURLToPath } from "node:url";
|
|
43
|
+
import YAML from "yaml";
|
|
44
|
+
|
|
45
|
+
import { cdnUrl } from "./site-theme-urls.mjs";
|
|
46
|
+
|
|
47
|
+
/** The directory holding the banner inventory and the kind mapping. */
|
|
48
|
+
export const BANNER_DATA_DIR = path.resolve(
|
|
49
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
50
|
+
"..",
|
|
51
|
+
"site-theme",
|
|
52
|
+
"data",
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
/** @type {{banners: object, kinds: object}|undefined} */
|
|
56
|
+
let shipped;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The banner data the theme ships.
|
|
60
|
+
*
|
|
61
|
+
* @returns {{banners: object, kinds: object}} The inventory and the kind mapping.
|
|
62
|
+
*/
|
|
63
|
+
export function shippedBannerData() {
|
|
64
|
+
shipped ??= {
|
|
65
|
+
banners: YAML.parse(fs.readFileSync(path.join(BANNER_DATA_DIR, "banners.yaml"), "utf8")),
|
|
66
|
+
kinds: YAML.parse(fs.readFileSync(path.join(BANNER_DATA_DIR, "banner-kinds.yaml"), "utf8")),
|
|
67
|
+
};
|
|
68
|
+
return shipped;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The text of a value as a template prints it, with an unset value as `<nil>`. */
|
|
72
|
+
const printed = (value) => (value === undefined || value === null ? "<nil>" : String(value));
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The address of a page's banner.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} site - The site: `params.cdnBaseURL` and `basePath`.
|
|
78
|
+
* @param {object} fields - What the page states.
|
|
79
|
+
* @param {object} [fields.data] - The page's `data`, read for the resolved `banner`.
|
|
80
|
+
* @param {unknown} [fields.banner] - The hand-authored `banner` value.
|
|
81
|
+
* @param {string} [fields.type] - The note `type`.
|
|
82
|
+
* @param {string} [fields.subType] - The note `subType`.
|
|
83
|
+
* @param {string} [fields.category] - The page's `category`.
|
|
84
|
+
* @param {string} [fields.subtype] - The name to default to when no `type` is given.
|
|
85
|
+
* @param {string} [fields.context] - The page address, named in a report.
|
|
86
|
+
* @param {object} [options] - Options.
|
|
87
|
+
* @param {{banners: object, kinds: object}} [options.data] - The inventory and the
|
|
88
|
+
* kind mapping. The shipped ones by default.
|
|
89
|
+
* @param {(finding: {severity: string, message: string, page?: string}) => void} [options.report] -
|
|
90
|
+
* Receives each name that misses the inventory, once.
|
|
91
|
+
* @param {Set<string>} [options.reported] - The names already reported.
|
|
92
|
+
* @returns {string} The address, or `""` when the page has no banner.
|
|
93
|
+
*/
|
|
94
|
+
export function bannerUrl(site, fields, { data = shippedBannerData(), report, reported } = {}) {
|
|
95
|
+
const banners = data.banners ?? {};
|
|
96
|
+
const kinds = data.kinds ?? {};
|
|
97
|
+
const dir = banners.dir || "images/banners";
|
|
98
|
+
const fallback = banners.fallback || "default.webp";
|
|
99
|
+
const available = banners.available ?? [];
|
|
100
|
+
|
|
101
|
+
let subtype = fields.subtype ?? "";
|
|
102
|
+
if (fields.type) {
|
|
103
|
+
const entry = (kinds.kinds ?? {})[fields.type];
|
|
104
|
+
if ((kinds.categoryTypes ?? []).includes(fields.type)) {
|
|
105
|
+
subtype = fields.category || "default";
|
|
106
|
+
} else if (entry && typeof entry === "object") {
|
|
107
|
+
subtype = entry[String(fields.subType ?? "").toLowerCase()] || fields.type;
|
|
108
|
+
} else {
|
|
109
|
+
subtype = entry || fields.type;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const raw = printed(fields.banner);
|
|
114
|
+
const declined = raw.toLowerCase() === "none" || raw === "false";
|
|
115
|
+
const resolved = fields.data ? fields.data.banner : undefined;
|
|
116
|
+
const fromBuild = resolved !== undefined && resolved !== null;
|
|
117
|
+
|
|
118
|
+
let banner = "";
|
|
119
|
+
let absolute = false;
|
|
120
|
+
if (fromBuild) {
|
|
121
|
+
banner = String(resolved);
|
|
122
|
+
absolute = true;
|
|
123
|
+
} else if (!declined) {
|
|
124
|
+
if (fields.banner) {
|
|
125
|
+
const value = String(fields.banner);
|
|
126
|
+
banner = `images/${value.replace(/^\//, "")}`;
|
|
127
|
+
if (/^(?:https?:)?\/\//.test(value)) {
|
|
128
|
+
banner = value;
|
|
129
|
+
absolute = true;
|
|
130
|
+
}
|
|
131
|
+
} else if (subtype) {
|
|
132
|
+
banner = `${dir}/${subtype}.webp`;
|
|
133
|
+
}
|
|
134
|
+
if (banner && !absolute && path.posix.dirname(banner) === dir) {
|
|
135
|
+
const name = path.posix.basename(banner, path.posix.extname(banner));
|
|
136
|
+
if (!available.includes(name)) {
|
|
137
|
+
if (!reported?.has(name)) {
|
|
138
|
+
reported?.add(name);
|
|
139
|
+
report?.({
|
|
140
|
+
severity: "warning",
|
|
141
|
+
page: fields.context || undefined,
|
|
142
|
+
message:
|
|
143
|
+
`hero-banner: no banner named "${name}" in the shared artwork set; ` +
|
|
144
|
+
`falling back to ${dir}/${fallback}. Draw it and add "${name}" to ` +
|
|
145
|
+
"data/banners.yaml, or name a hero image with `data.banner`. " +
|
|
146
|
+
"Write `data.banner: none` to decline a hero image.",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
banner = `${dir}/${fallback}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return banner ? cdnUrl(site, banner) : "";
|
|
154
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **Addresses in the site layouts.** The layouts carry no address of their own:
|
|
16
|
+
* every URL they write comes from the site's configuration or from a page's
|
|
17
|
+
* data, and a site served under a path prefix such as `/thalorna/` needs its own
|
|
18
|
+
* links to carry the prefix without writing it down anywhere.
|
|
19
|
+
*
|
|
20
|
+
* | Function | Resolves |
|
|
21
|
+
* | -------- | -------- |
|
|
22
|
+
* | {@link relUrl} | a path inside the site, under its base path |
|
|
23
|
+
* | {@link cdnUrl} | an asset path, against `params.cdnBaseURL` when the site has one |
|
|
24
|
+
* | {@link siteUrl} | a consumer-supplied link: absolute, a fragment, or a path in the site |
|
|
25
|
+
* | {@link safeUrl} | a link from data, refused when its scheme could run script |
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { slugify } from "./content-slug.mjs";
|
|
31
|
+
|
|
32
|
+
/** Whether an address names a host: `http://`, `https://` or `//`. */
|
|
33
|
+
export function isAbsoluteUrl(url) {
|
|
34
|
+
return /^(?:https?:)?\/\//.test(String(url ?? ""));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The path a site is served under, with a leading and a trailing slash.
|
|
39
|
+
*
|
|
40
|
+
* @param {object} site - The site; `baseURL` is its address.
|
|
41
|
+
* @returns {string} `/` for a site at its origin's root, `/thalorna/` for one under a prefix.
|
|
42
|
+
*/
|
|
43
|
+
export function basePath(site) {
|
|
44
|
+
if (!site.baseURL) return "/";
|
|
45
|
+
const { pathname } = new URL(site.baseURL, "https://site.invalid");
|
|
46
|
+
return pathname.endsWith("/") ? pathname : `${pathname}/`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A path inside the site, under its base path.
|
|
51
|
+
*
|
|
52
|
+
* A leading slash is dropped first: it would otherwise read as "from the
|
|
53
|
+
* origin" and leave the base path off.
|
|
54
|
+
*
|
|
55
|
+
* @param {object} site - The site.
|
|
56
|
+
* @param {string} [target] - The path; empty for the site's root.
|
|
57
|
+
* @returns {string} The address.
|
|
58
|
+
*/
|
|
59
|
+
export function relUrl(site, target = "") {
|
|
60
|
+
return `${basePath(site)}${String(target).replace(/^\/+/, "")}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* An asset path, resolved against the site's asset host.
|
|
65
|
+
*
|
|
66
|
+
* An absolute address is returned as written. Otherwise the path is joined onto
|
|
67
|
+
* `params.cdnBaseURL`, and with none set it is a path inside the site.
|
|
68
|
+
*
|
|
69
|
+
* @param {object} site - The site; `params.cdnBaseURL` is the asset root.
|
|
70
|
+
* @param {string} target - The asset path.
|
|
71
|
+
* @returns {string} The address.
|
|
72
|
+
*/
|
|
73
|
+
export function cdnUrl(site, target) {
|
|
74
|
+
if (isAbsoluteUrl(target)) return target;
|
|
75
|
+
const rel = String(target).replace(/^\/+/, "");
|
|
76
|
+
const root = site.params?.cdnBaseURL;
|
|
77
|
+
return root ? `${String(root).replace(/\/+$/, "")}/${rel}` : relUrl(site, rel);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A link a consuming site supplied, resolved against the site it is rendered on.
|
|
82
|
+
*
|
|
83
|
+
* An absolute address, a fragment and a query are returned as written; anything
|
|
84
|
+
* else is a path inside the site.
|
|
85
|
+
*
|
|
86
|
+
* @param {object} site - The site.
|
|
87
|
+
* @param {string} [url] - The link; empty for the site's root.
|
|
88
|
+
* @returns {string} The address.
|
|
89
|
+
*/
|
|
90
|
+
export function siteUrl(site, url = "") {
|
|
91
|
+
const value = String(url ?? "");
|
|
92
|
+
return isAbsoluteUrl(value) || value.startsWith("#") || value.startsWith("?") ?
|
|
93
|
+
value
|
|
94
|
+
: relUrl(site, value);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A scheme that can run script or load a document of the page's own origin. */
|
|
98
|
+
const UNSAFE_SCHEME = /^\s*(?:javascript|vbscript|data):/i;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A link from a page's data, with a scheme that could run script refused.
|
|
102
|
+
*
|
|
103
|
+
* @param {unknown} url - The link as the data states it.
|
|
104
|
+
* @returns {string} The link, or `#` when its scheme is not one a reader should follow.
|
|
105
|
+
*/
|
|
106
|
+
export function safeUrl(url) {
|
|
107
|
+
const value = String(url ?? "");
|
|
108
|
+
// Control characters and spaces are dropped by a browser inside a scheme.
|
|
109
|
+
const bare = value.replace(/[\u0000- ]/g, "");
|
|
110
|
+
return UNSAFE_SCHEME.test(bare) ? "#" : value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The address of a tag's listing page.
|
|
115
|
+
*
|
|
116
|
+
* One function writes it for the link in a page's tag row and for the page the
|
|
117
|
+
* site build writes, so the two cannot disagree.
|
|
118
|
+
*
|
|
119
|
+
* @param {object} site - The site.
|
|
120
|
+
* @param {string} tag - The tag.
|
|
121
|
+
* @returns {string} The address.
|
|
122
|
+
*/
|
|
123
|
+
export function tagUrl(site, tag) {
|
|
124
|
+
return relUrl(site, `tags/${slugify(tag)}/`);
|
|
125
|
+
}
|