@apleasantview/eleventy-plugin-baseline 0.1.0-next.40 → 0.1.0-next.41
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/README.md +21 -23
- package/core/back-compat/options.js +69 -0
- package/core/content-graph/backlinks.js +65 -0
- package/core/content-graph/extractors.js +140 -0
- package/core/content-graph/graph.js +118 -0
- package/core/content-graph/index.js +2 -0
- package/core/content-graph/prepass.js +121 -0
- package/core/logging/banner.js +49 -0
- package/core/{logging.js → logging/index.js} +19 -2
- package/core/logging/quips.js +30 -0
- package/core/markdown/auto-heading-ids.js +86 -0
- package/core/markdown/index.js +5 -0
- package/core/markdown/safe-use.js +42 -0
- package/core/{wikilinks.js → markdown/wikilinks.js} +3 -3
- package/core/page-context/build.js +239 -0
- package/core/page-context/index.js +1 -0
- package/core/page-context/register.js +73 -0
- package/core/page-context/seo-helpers.js +56 -0
- package/core/schema.js +19 -1
- package/core/slug-index.js +2 -2
- package/core/state.js +73 -0
- package/core/{shortcodes/image.js → surface/image-shortcode.js} +4 -4
- package/core/surface/index.js +22 -0
- package/core/utils/add-trailing-slash.js +11 -0
- package/core/utils/ensure-dot-slash-dir.js +13 -0
- package/core/utils/normalize-languages.js +28 -0
- package/core/utils/resolve-field.js +9 -0
- package/core/utils/resolve-subdir.js +20 -0
- package/core/utils/slugify.js +15 -0
- package/core/utils/unique-by.js +25 -0
- package/core/virtual-dir.js +11 -10
- package/index.js +152 -115
- package/modules/assets/index.js +4 -2
- package/modules/assets/processors/esbuild-process.js +2 -2
- package/modules/assets/processors/postcss-process.js +2 -2
- package/modules/head/drivers/posthtml-head-elements.js +1 -3
- package/modules/head/index.js +7 -10
- package/modules/multilang/index.js +4 -2
- package/modules/navigator/index.js +33 -20
- package/modules/navigator/templates/navigator-core.html +1 -1
- package/modules/sitemap/index.js +7 -3
- package/package.json +4 -2
- package/core/filters/index.js +0 -4
- package/core/global-functions/index.js +0 -6
- package/core/page-context.js +0 -310
- package/core/shortcodes/index.js +0 -2
- package/core/utils/helpers.js +0 -75
- /package/core/{filters/markdown.js → markdown/markdownify.js} +0 -0
- /package/core/{filters → surface/filters}/isString.js +0 -0
- /package/core/{filters → surface/filters}/related-posts.js +0 -0
- /package/core/{global-functions/date.js → surface/global-date-function.js} +0 -0
package/modules/sitemap/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { normalizeLanguages } from '../../core/utils/
|
|
4
|
+
import { normalizeLanguages } from '../../core/utils/normalize-languages.js';
|
|
5
5
|
|
|
6
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
7
|
const __dirname = path.dirname(__filename);
|
|
@@ -19,7 +19,7 @@ const __dirname = path.dirname(__filename);
|
|
|
19
19
|
*
|
|
20
20
|
* System role:
|
|
21
21
|
* Reads the same normalised language map as multilang (via
|
|
22
|
-
* core/utils/
|
|
22
|
+
* core/utils/normalize-languages.js) and emits virtual templates that Eleventy
|
|
23
23
|
* renders to XML. Pages opt out via `noindex` in the cascade.
|
|
24
24
|
*
|
|
25
25
|
* Lifecycle:
|
|
@@ -37,7 +37,7 @@ const __dirname = path.dirname(__filename);
|
|
|
37
37
|
* Owns computed page.sitemap and the virtual sitemap templates
|
|
38
38
|
* (single-language /sitemap.xml, or per-lang /{lang}/sitemap.xml plus a
|
|
39
39
|
* /sitemap.xml index).
|
|
40
|
-
* Does not own language normalisation (core/utils/
|
|
40
|
+
* Does not own language normalisation (core/utils/normalize-languages.js) or noindex
|
|
41
41
|
* propagation through the cascade.
|
|
42
42
|
*
|
|
43
43
|
* Data flow:
|
|
@@ -108,6 +108,8 @@ export function sitemapCore(eleventyConfig, moduleContext) {
|
|
|
108
108
|
languages: languages,
|
|
109
109
|
_internal: true
|
|
110
110
|
});
|
|
111
|
+
|
|
112
|
+
log.info('Sitemaps written per-language');
|
|
111
113
|
} else {
|
|
112
114
|
eleventyConfig.addTemplate('_baseline/sitemap-core.html', baseContent, {
|
|
113
115
|
permalink: '/sitemap.xml',
|
|
@@ -117,5 +119,7 @@ export function sitemapCore(eleventyConfig, moduleContext) {
|
|
|
117
119
|
eleventyExcludeFromCollections: true,
|
|
118
120
|
_internal: true
|
|
119
121
|
});
|
|
122
|
+
|
|
123
|
+
log.info('Sitemap written');
|
|
120
124
|
}
|
|
121
125
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apleasantview/eleventy-plugin-baseline",
|
|
3
|
-
"version": "0.1.0-next.
|
|
3
|
+
"version": "0.1.0-next.41",
|
|
4
4
|
"description": "An experimental Swiss army knife toolkit for Eleventy",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"bugs": {
|
|
35
35
|
"url": "https://github.com/apleasantview/eleventy-plugin-baseline/issues"
|
|
36
36
|
},
|
|
37
|
-
"homepage": "https://eleventy-
|
|
37
|
+
"homepage": "https://www.eleventy-baseline.dev/",
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@11ty/eleventy": ">=3.1.0",
|
|
40
40
|
"@11ty/eleventy-img": "^6.0.4"
|
|
@@ -46,8 +46,10 @@
|
|
|
46
46
|
"dotenv": "^17.2.3",
|
|
47
47
|
"esbuild": "0.27.0",
|
|
48
48
|
"kleur": "^4.1.5",
|
|
49
|
+
"linkedom": "^0.18.12",
|
|
49
50
|
"luxon": "^3.7.2",
|
|
50
51
|
"markdown-it": "^14.1.1",
|
|
52
|
+
"markdown-it-attrs": "^4.3.1",
|
|
51
53
|
"postcss": "^8.5.6",
|
|
52
54
|
"postcss-import": "^16.1.1",
|
|
53
55
|
"postcss-import-ext-glob": "^2.1.1",
|
package/core/filters/index.js
DELETED
package/core/page-context.js
DELETED
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
import pick from './utils/pick.js';
|
|
2
|
-
import { slugify } from './utils/helpers.js';
|
|
3
|
-
import { createLogger } from './logging.js';
|
|
4
|
-
import { getScope, memoize, setEntry } from './registry.js';
|
|
5
|
-
|
|
6
|
-
const SCOPE_NAME = 'core:page-context';
|
|
7
|
-
const COMPUTED_KEY = 'eleventyComputed._pageContext';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Page context (runtime substrate)
|
|
11
|
-
*
|
|
12
|
-
* A normalised per-page object built once at cascade-time and cached for
|
|
13
|
-
* transform-time consumers. The shape downstream modules read instead of
|
|
14
|
-
* re-deriving from raw cascade data.
|
|
15
|
-
*
|
|
16
|
-
* Architecture layer:
|
|
17
|
-
* runtime substrate
|
|
18
|
-
*
|
|
19
|
-
* System role:
|
|
20
|
-
* Lifecycle bridge between Eleventy's data cascade and the htmlTransformer.
|
|
21
|
-
* Head reads it via `getByKey`; navigator snapshots it for inspection.
|
|
22
|
-
*
|
|
23
|
-
* Lifecycle:
|
|
24
|
-
* cascade-time → eleventyComputed._pageContext builds and caches the context
|
|
25
|
-
* transform-time → consumers retrieve the cached context by page.url
|
|
26
|
-
*
|
|
27
|
-
* Why this exists:
|
|
28
|
-
* Eleventy's htmlTransformer context exposes only page metadata, not the
|
|
29
|
-
* data cascade. The cache lets transform-time consumers read the same
|
|
30
|
-
* normalised view that cascade-time produced.
|
|
31
|
-
*
|
|
32
|
-
* Scope:
|
|
33
|
-
* Owns the page-context shape, memoisation, key-based lookup, and snapshot.
|
|
34
|
-
* Does not own the meaning of any field; modules consume them as they see fit.
|
|
35
|
-
* Templates with `_internal: true` are skipped (synthetic sitemap pages, etc.).
|
|
36
|
-
*
|
|
37
|
-
* Data flow:
|
|
38
|
-
* data cascade → buildPageContext → registry scope → head, navigator
|
|
39
|
-
*
|
|
40
|
-
* @param {import("@11ty/eleventy").UserConfig} eleventyConfig
|
|
41
|
-
* @param {Object} coreContext - Resolved baseline core context (state, runtime, helpers).
|
|
42
|
-
*/
|
|
43
|
-
export function registerPageContext(eleventyConfig, coreContext) {
|
|
44
|
-
const { state, runtime, site } = coreContext;
|
|
45
|
-
const { slugIndex } = runtime;
|
|
46
|
-
const { settings, options } = state;
|
|
47
|
-
|
|
48
|
-
const log = createLogger(SCOPE_NAME, { verbose: options.verbose });
|
|
49
|
-
const scope = getScope(eleventyConfig, SCOPE_NAME);
|
|
50
|
-
|
|
51
|
-
// Head options.
|
|
52
|
-
const separator = options.head?.titleSeparator ?? ' – ';
|
|
53
|
-
const generator = options.head?.showGenerator ?? false;
|
|
54
|
-
|
|
55
|
-
function shouldSkip(data) {
|
|
56
|
-
if (data._internal) return true;
|
|
57
|
-
if (data.page?.outputFileExtension !== 'html') return true;
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// --- Helpers ---
|
|
62
|
-
const uniqueBy = (arr, keyFn) =>
|
|
63
|
-
Object.values(
|
|
64
|
-
(arr ?? []).reduce((acc, item) => {
|
|
65
|
-
if (!item) return acc;
|
|
66
|
-
|
|
67
|
-
const id = typeof keyFn === 'function' ? keyFn(item) : item?.[keyFn];
|
|
68
|
-
|
|
69
|
-
if (!id) {
|
|
70
|
-
acc[JSON.stringify(item)] = item;
|
|
71
|
-
return acc;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
acc[id] = item;
|
|
75
|
-
return acc;
|
|
76
|
-
}, {})
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
// --- SEO helpers ---
|
|
80
|
-
function stripTrackingParams(urlObj) {
|
|
81
|
-
['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid'].forEach((p) =>
|
|
82
|
-
urlObj.searchParams.delete(p)
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
urlObj.hash = '';
|
|
86
|
-
return urlObj;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function extractFirstParagraph(data) {
|
|
90
|
-
const html = data?.content;
|
|
91
|
-
if (!html) return null;
|
|
92
|
-
const match = html.match(/<p>(.*?)<\/p>/i);
|
|
93
|
-
return match?.[1] ?? null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function normalizeCanonical(path, siteUrl) {
|
|
97
|
-
if (!path || !siteUrl) return null;
|
|
98
|
-
|
|
99
|
-
const url = new URL(path, siteUrl);
|
|
100
|
-
|
|
101
|
-
url.hash = '';
|
|
102
|
-
|
|
103
|
-
return stripTrackingParams(url).href;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// --- Field resolver ---
|
|
107
|
-
function resolveField({ pageValue, siteValue, fallbackValue, isHome }) {
|
|
108
|
-
let value = pageValue ?? siteValue ?? fallbackValue ?? null;
|
|
109
|
-
|
|
110
|
-
return value;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// --- Builders ---
|
|
114
|
-
function buildSite(lang, userSettings) {
|
|
115
|
-
const langEntry = lang ? userSettings.languages?.[lang] : undefined;
|
|
116
|
-
return {
|
|
117
|
-
title: langEntry?.title ?? userSettings.title ?? '',
|
|
118
|
-
tagline: langEntry?.tagline ?? userSettings.tagline ?? '',
|
|
119
|
-
description: langEntry?.description ?? userSettings.description ?? '',
|
|
120
|
-
url: userSettings.url ?? '',
|
|
121
|
-
noindex: userSettings.noindex === true
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function buildPage(pageInput) {
|
|
126
|
-
return {
|
|
127
|
-
inputPath: pageInput?.inputPath ?? null,
|
|
128
|
-
fileSlug: pageInput?.fileSlug ?? null,
|
|
129
|
-
filePathStem: pageInput?.filePathStem ?? null,
|
|
130
|
-
outputFileExtension: pageInput?.outputFileExtension ?? null,
|
|
131
|
-
templateSyntax: pageInput?.templateSyntax ?? null,
|
|
132
|
-
date: pageInput?.date ?? null,
|
|
133
|
-
url: pageInput?.url ?? null,
|
|
134
|
-
outputPath: pageInput?.outputPath ?? null,
|
|
135
|
-
lang: pageInput?.lang ?? null,
|
|
136
|
-
locale: pageInput?.locale ?? null,
|
|
137
|
-
sitemap: pageInput?.sitemap ?? null
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function buildEntry(data) {
|
|
142
|
-
const rawSlug = data?.slug ?? data?.page?.fileSlug;
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
title: data?.seo?.title ?? data?.title ?? null,
|
|
146
|
-
description: data?.seo?.description ?? data?.description ?? null,
|
|
147
|
-
excerpt: data?.excerpt ?? null,
|
|
148
|
-
slug: slugify(rawSlug),
|
|
149
|
-
head: data?.head ?? null
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function buildQuery({ entry, page }) {
|
|
154
|
-
return {
|
|
155
|
-
isHome: page.url === '/'
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function buildMeta({ data, site, page, query }) {
|
|
160
|
-
const noindex = site.noindex || data?.noindex === true;
|
|
161
|
-
|
|
162
|
-
const robots = noindex
|
|
163
|
-
? 'noindex, nofollow'
|
|
164
|
-
: 'index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1';
|
|
165
|
-
|
|
166
|
-
const contentMap = runtime.contentMap;
|
|
167
|
-
|
|
168
|
-
const siteTitle = site.title;
|
|
169
|
-
const siteDescription = site.description;
|
|
170
|
-
const tagline = site.tagline;
|
|
171
|
-
|
|
172
|
-
const pageTitle = data?.seo?.title ?? data?.title ?? siteTitle;
|
|
173
|
-
const pageDescription = data?.seo?.description ?? data?.description ?? data?.excerpt ?? extractFirstParagraph(data);
|
|
174
|
-
|
|
175
|
-
function enhance(value) {
|
|
176
|
-
if (query.isHome && !data?.seo?.title && tagline) {
|
|
177
|
-
return `${siteTitle}${separator}${tagline}`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (!query.isHome && pageTitle && siteTitle && pageTitle !== siteTitle) {
|
|
181
|
-
return `${pageTitle}${separator}${siteTitle}`;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return value;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ---- DESCRIPTION ----
|
|
188
|
-
const description = resolveField({
|
|
189
|
-
pageValue: pageDescription,
|
|
190
|
-
siteValue: siteDescription,
|
|
191
|
-
isHome: query.isHome
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// ---- TITLE ----
|
|
195
|
-
const base = resolveField({
|
|
196
|
-
pageValue: pageTitle,
|
|
197
|
-
siteValue: siteTitle
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
const title = enhance(base);
|
|
201
|
-
|
|
202
|
-
// ---- CANONICAL ----
|
|
203
|
-
let canonical = null;
|
|
204
|
-
|
|
205
|
-
if (!noindex) {
|
|
206
|
-
const rawCanonical =
|
|
207
|
-
data?.canonical ?? page.url ?? (page.inputPath && contentMap?.inputPathToUrl?.[page.inputPath]?.[0]);
|
|
208
|
-
|
|
209
|
-
canonical = normalizeCanonical(rawCanonical, site.url);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
title,
|
|
214
|
-
description,
|
|
215
|
-
canonical,
|
|
216
|
-
robots,
|
|
217
|
-
noindex
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function buildRender(data) {
|
|
222
|
-
return {
|
|
223
|
-
generator: data?.eleventy?.generator ?? null
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// HEAD (global + page-level merge + dedupe)
|
|
228
|
-
function buildHead({ userSettings, data }) {
|
|
229
|
-
const userHead = userSettings.head ?? {};
|
|
230
|
-
const pageHead = data?.head ?? {};
|
|
231
|
-
|
|
232
|
-
const link = uniqueBy([...(userHead.link ?? []), ...(pageHead.link ?? [])], (item) => {
|
|
233
|
-
if (item?.rel === 'canonical') {
|
|
234
|
-
try {
|
|
235
|
-
return normalizeCanonical(item.href, site.url);
|
|
236
|
-
} catch {
|
|
237
|
-
return item?.href;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return item?.href;
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
const script = uniqueBy([...(userHead.script ?? []), ...(pageHead.script ?? [])], 'src');
|
|
244
|
-
|
|
245
|
-
const style = uniqueBy([...(userHead.style ?? []), ...(pageHead.style ?? [])], 'href');
|
|
246
|
-
|
|
247
|
-
const meta = uniqueBy([...(userHead.meta ?? []), ...(pageHead.meta ?? [])], 'name');
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
link,
|
|
251
|
-
script,
|
|
252
|
-
style,
|
|
253
|
-
meta
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Main context builder.
|
|
259
|
-
* Pure transformation: Eleventy data → normalised page context.
|
|
260
|
-
*/
|
|
261
|
-
function buildPageContext(data) {
|
|
262
|
-
const pageInput = data.page ?? {};
|
|
263
|
-
const userSettings = data.settings ?? settings;
|
|
264
|
-
|
|
265
|
-
const page = buildPage(pageInput);
|
|
266
|
-
const site = buildSite(page.lang, userSettings);
|
|
267
|
-
const entry = buildEntry(data);
|
|
268
|
-
const query = buildQuery({ entry, page });
|
|
269
|
-
const meta = buildMeta({ data, site, page, query });
|
|
270
|
-
const render = buildRender(data);
|
|
271
|
-
const head = buildHead({ userSettings, data });
|
|
272
|
-
|
|
273
|
-
const context = {
|
|
274
|
-
site,
|
|
275
|
-
page,
|
|
276
|
-
entry,
|
|
277
|
-
query,
|
|
278
|
-
meta,
|
|
279
|
-
render,
|
|
280
|
-
head
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
const inspectionKey = context.page.url ?? context.page.inputPath;
|
|
284
|
-
if (inspectionKey) setEntry(scope, inspectionKey, context);
|
|
285
|
-
|
|
286
|
-
if (slugIndex && entry.slug && page.url) {
|
|
287
|
-
const eligible = page.locale?.isDefaultLang === true;
|
|
288
|
-
if (eligible) {
|
|
289
|
-
slugIndex.set(entry.slug, page.url, page.inputPath);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return context;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
eleventyConfig.addGlobalData(COMPUTED_KEY, () => {
|
|
297
|
-
return (data) => {
|
|
298
|
-
if (shouldSkip(data)) return null;
|
|
299
|
-
return memoize(scope, data, buildPageContext);
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
log.info('Page context added to the data cascade and registry exposed');
|
|
304
|
-
|
|
305
|
-
return {
|
|
306
|
-
get: (data) => scope.cache.get(data),
|
|
307
|
-
getByKey: (key) => scope.values.get(key),
|
|
308
|
-
snapshot: () => Object.fromEntries(scope.values)
|
|
309
|
-
};
|
|
310
|
-
}
|
package/core/shortcodes/index.js
DELETED
package/core/utils/helpers.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { TemplatePath } from '@11ty/eleventy-utils';
|
|
2
|
-
import slugifyLib from 'slugify';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Helper function to add trailing slash to a path
|
|
6
|
-
* @param {string} path
|
|
7
|
-
* @returns {string}
|
|
8
|
-
*/
|
|
9
|
-
export function addTrailingSlash(path) {
|
|
10
|
-
if (path.slice(-1) === '/') {
|
|
11
|
-
return path;
|
|
12
|
-
}
|
|
13
|
-
return path + '/';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Resolve a subdirectory under input and output.
|
|
18
|
-
* Joins inputDir/outputDir with rawDir, normalises, and adds trailing slashes.
|
|
19
|
-
* @param {string} inputDir - The input directory (e.g., "./src/").
|
|
20
|
-
* @param {string} outputDir - The output directory (e.g., "./dist/").
|
|
21
|
-
* @param {string} rawDir - Raw subdirectory value (e.g., "assets", "static").
|
|
22
|
-
* @returns {{input: string, output: string}}
|
|
23
|
-
*/
|
|
24
|
-
export function resolveSubdir(inputDir, outputDir, rawDir) {
|
|
25
|
-
const joinedInput = TemplatePath.join(inputDir, rawDir || '');
|
|
26
|
-
const joinedOutput = TemplatePath.join(outputDir, rawDir || '');
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
input: addTrailingSlash(TemplatePath.standardizeFilePath(joinedInput)),
|
|
30
|
-
output: addTrailingSlash(TemplatePath.standardizeFilePath(joinedOutput))
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Slugify a string into a wikilink-friendly key.
|
|
36
|
-
* Lowercases, strips diacritics, replaces non-alphanumerics with hyphens,
|
|
37
|
-
* trims leading/trailing hyphens. Returns null for empty input.
|
|
38
|
-
*
|
|
39
|
-
* @param {string|null|undefined} input
|
|
40
|
-
* @returns {string|null}
|
|
41
|
-
*/
|
|
42
|
-
export function slugify(input) {
|
|
43
|
-
if (input == null) return null;
|
|
44
|
-
const slug = slugifyLib(String(input), { lower: true, strict: true, trim: true });
|
|
45
|
-
return slug || null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Normalize language input to an object map.
|
|
50
|
-
* Accepts an array of language codes or an object keyed by language code.
|
|
51
|
-
* Returns null if input is invalid or empty.
|
|
52
|
-
*
|
|
53
|
-
* @param {Object} settings - Options object containing languages.
|
|
54
|
-
* @param {import('../logging.js').BaselineLogger} [logger] - Logger for dropped-entry notice.
|
|
55
|
-
* @returns {Record<string, Object>|null} Normalized language map, or null.
|
|
56
|
-
*/
|
|
57
|
-
export function normalizeLanguages(settings, logger) {
|
|
58
|
-
const normalizedLanguages = Array.isArray(settings.languages)
|
|
59
|
-
? Object.fromEntries(
|
|
60
|
-
settings.languages
|
|
61
|
-
.filter((lang) => typeof lang === 'string' && lang.trim())
|
|
62
|
-
.map((lang) => [lang.toLowerCase().trim(), {}])
|
|
63
|
-
)
|
|
64
|
-
: settings.languages && typeof settings.languages === 'object'
|
|
65
|
-
? settings.languages
|
|
66
|
-
: null;
|
|
67
|
-
|
|
68
|
-
if (logger && Array.isArray(settings.languages)) {
|
|
69
|
-
const normalizedCount = normalizedLanguages ? Object.keys(normalizedLanguages).length : 0;
|
|
70
|
-
if (normalizedCount !== settings.languages.length) {
|
|
71
|
-
logger.info('Some languages entries were invalid and were dropped.');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return normalizedLanguages;
|
|
75
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|