@apleasantview/eleventy-plugin-baseline 0.1.0-next.39 → 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.
Files changed (54) hide show
  1. package/README.md +21 -23
  2. package/core/back-compat/options.js +69 -0
  3. package/core/content-graph/backlinks.js +65 -0
  4. package/core/content-graph/extractors.js +140 -0
  5. package/core/content-graph/graph.js +118 -0
  6. package/core/content-graph/index.js +2 -0
  7. package/core/content-graph/prepass.js +121 -0
  8. package/core/logging/banner.js +49 -0
  9. package/core/logging/index.js +80 -0
  10. package/core/logging/quips.js +30 -0
  11. package/core/markdown/auto-heading-ids.js +86 -0
  12. package/core/markdown/index.js +5 -0
  13. package/core/markdown/safe-use.js +42 -0
  14. package/core/{wikilinks.js → markdown/wikilinks.js} +6 -6
  15. package/core/page-context/build.js +239 -0
  16. package/core/page-context/index.js +1 -0
  17. package/core/page-context/register.js +73 -0
  18. package/core/page-context/seo-helpers.js +56 -0
  19. package/core/schema.js +52 -1
  20. package/core/slug-index.js +2 -2
  21. package/core/state.js +73 -0
  22. package/core/{shortcodes/image.js → surface/image-shortcode.js} +4 -4
  23. package/core/surface/index.js +22 -0
  24. package/core/types.js +1 -1
  25. package/core/utils/add-trailing-slash.js +11 -0
  26. package/core/utils/ensure-dot-slash-dir.js +13 -0
  27. package/core/utils/normalize-languages.js +28 -0
  28. package/core/utils/resolve-field.js +9 -0
  29. package/core/utils/resolve-subdir.js +20 -0
  30. package/core/utils/slugify.js +15 -0
  31. package/core/utils/unique-by.js +25 -0
  32. package/core/virtual-dir.js +11 -10
  33. package/index.js +152 -115
  34. package/modules/assets/index.js +4 -2
  35. package/modules/assets/processors/esbuild-process.js +35 -2
  36. package/modules/assets/processors/postcss-process.js +36 -2
  37. package/modules/head/drivers/capo-adapter.js +26 -4
  38. package/modules/head/drivers/posthtml-head-elements.js +2 -4
  39. package/modules/head/index.js +7 -10
  40. package/modules/multilang/index.js +4 -2
  41. package/modules/navigator/index.js +33 -20
  42. package/modules/navigator/templates/navigator-core.html +1 -1
  43. package/modules/sitemap/index.js +7 -3
  44. package/package.json +4 -2
  45. package/core/filters/index.js +0 -4
  46. package/core/global-functions/index.js +0 -6
  47. package/core/logging.js +0 -32
  48. package/core/page-context.js +0 -310
  49. package/core/shortcodes/index.js +0 -2
  50. package/core/utils/helpers.js +0 -75
  51. /package/core/{filters/markdown.js → markdown/markdownify.js} +0 -0
  52. /package/core/{filters → surface/filters}/isString.js +0 -0
  53. /package/core/{filters → surface/filters}/related-posts.js +0 -0
  54. /package/core/{global-functions/date.js → surface/global-date-function.js} +0 -0
@@ -10,39 +10,42 @@ const __dirname = path.dirname(__filename);
10
10
  /**
11
11
  * Navigator (module)
12
12
  *
13
- * Debug surface. Exposes Eleventy and Baseline runtime state to templates so
14
- * developers can inspect data shape, scope contents, and lifecycle output
15
- * without leaving the page.
13
+ * Two roles, one module: the public read surface for plugin-produced
14
+ * cross-page data (the content graph and its enriched backlinks), and the
15
+ * debug surface for inspecting Eleventy and Baseline runtime state.
16
16
  *
17
17
  * Architecture layer:
18
18
  * module
19
19
  *
20
20
  * System role:
21
- * Read-only window into the runtime substrate. Pulls snapshots from the
22
- * page-context registry and content-map store via the module context;
23
- * does not write back.
21
+ * Read-only window over the runtime substrate. Surfaces the content graph
22
+ * for templates that need cross-page reads, and snapshots from the
23
+ * page-context registry and content-map store for debugging. Writes
24
+ * nothing back.
24
25
  *
25
26
  * Lifecycle:
26
- * build-time → register Nunjucks globals, debug filters, and the
27
- * optional virtual debug page
27
+ * build-time → register `_navigator` ({ nodes, edges, backlinks }),
28
+ * debug globals, filters, and the optional virtual debug page
28
29
  * cascade-time → eleventyComputed `_snapshot` resolves contentMap and
29
30
  * pageContext on each page
30
31
  *
31
32
  * Why this exists:
32
- * Render-time inspection of cascade state has no built-in surface.
33
- * Centralising globals and filters under a debug-only module keeps the
34
- * inspection vocabulary stable and out of feature modules.
33
+ * Templates need an addressable cross-page surface for graph reads, and
34
+ * render-time inspection of cascade state has no built-in equivalent.
35
+ * One module owns both vocabularies so feature modules stay narrow.
35
36
  *
36
37
  * Scope:
37
- * Owns the `_runtime` and `_ctx` Nunjucks globals, computed `_snapshot`,
38
+ * Owns the `_navigator` global (`{ nodes, edges, backlinks }`, the public
39
+ * read surface), the debug globals `_runtime` and `_ctx`, computed `_snapshot`,
38
40
  * debug filters (`_inspect`, `_json`, `_keys`), and the optional virtual
39
41
  * page at /navigator-core.html.
40
- * Does not own the data it surfaces (page-context registry, content-map
41
- * store).
42
+ * Does not own the data it surfaces (content graph, page-context registry,
43
+ * content-map store).
42
44
  *
43
45
  * Data flow:
44
- * snapshots (contentMap, pageContext) + this.ctx → globals + computed
45
- * `_snapshot` + virtual page → developer
46
+ * runtime.contentGraph + snapshots + this.ctx → `_navigator` + debug
47
+ * globals + computed `_snapshot` + virtual page → templates and
48
+ * developers
46
49
  *
47
50
  * Note: `_snapshot.contentMap` is null on the navigator template itself
48
51
  * because it renders before `eleventy.contentMap` fires. Read `_snapshot`
@@ -51,23 +54,25 @@ const __dirname = path.dirname(__filename);
51
54
  * @param {import("@11ty/eleventy").UserConfig} eleventyConfig
52
55
  * @param {Object} moduleContext
53
56
  * @param {Object} moduleContext.state - Resolved plugin state.
57
+ * @param {Object} moduleContext.runtime - Lazy access layer; reads contentGraph.
54
58
  * @param {Object} moduleContext.snapshots - Thunks: { contentMap, pageContext }.
55
59
  */
56
60
  export function navigatorCore(eleventyConfig, moduleContext) {
57
- const { state, snapshots, log, env } = moduleContext;
61
+ const { state, runtime, snapshots, log, env } = moduleContext;
58
62
  const { settings, options } = state;
59
63
 
60
64
  // Structural-only options check: log on mismatch, do not throw.
61
65
  const parsed = optionsSchema.safeParse(options.navigator);
62
66
  if (!parsed.success) {
63
67
  for (const issue of parsed.error.issues) {
64
- log.info('options:', `${issue.path.join('.')} ${issue.message}`);
68
+ log.info('options:', `${issue.path.join('.')}, ${issue.message}`);
65
69
  }
66
70
  }
67
71
 
68
72
  // Boolean shorthand activates the virtual page; object form lets users tune.
69
73
  const navigatorOpts = options.navigator && typeof options.navigator === 'object' ? options.navigator : {};
70
- const renderTemplate = env.mode === 'development' ?? navigatorOpts.template ?? Boolean(options.navigator);
74
+ const renderTemplate =
75
+ navigatorOpts.template ?? (typeof options.navigator === 'boolean' ? options.navigator : env.mode === 'development');
71
76
  const inspectorDepth = navigatorOpts.inspectorDepth ?? 4;
72
77
 
73
78
  eleventyConfig.addGlobalData('eleventyComputed._snapshot', () => {
@@ -77,6 +82,14 @@ export function navigatorCore(eleventyConfig, moduleContext) {
77
82
  });
78
83
  });
79
84
 
85
+ // Public read surface for plugin-produced cross-page data. Templates can
86
+ // paginate over `_navigator.backlinks` or read `_navigator.graph` directly.
87
+ eleventyConfig.addGlobalData('_navigator', () => ({
88
+ nodes: runtime.contentGraph?.nodes ?? {},
89
+ edges: runtime.contentGraph?.edges ?? {},
90
+ backlinks: runtime.contentGraph?.backlinks ?? {}
91
+ }));
92
+
80
93
  /**
81
94
  * Nunjucks Global: _runtime
82
95
  *
@@ -125,7 +138,7 @@ export function navigatorCore(eleventyConfig, moduleContext) {
125
138
  inspectorDepth
126
139
  });
127
140
 
128
- log.info('Navigator template registered at /navigator-core.html');
141
+ log.info('Navigator mounted at /navigator-core.html');
129
142
  }
130
143
 
131
144
  /**
@@ -41,7 +41,7 @@ permalink: /navigator-core.html
41
41
  <details>
42
42
  <summary><strong>Page Context</strong></summary>
43
43
  <pre>
44
- {{- _snapshot.pageContext | _inspect({ depth: null }) -}}
44
+ {{- _pageContext | _inspect({ depth: null }) -}}
45
45
  </pre>
46
46
  </details>
47
47
  {% for key, value in _runtime() %}
@@ -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/helpers.js';
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/helpers.js) and emits virtual templates that Eleventy
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/helpers.js) or noindex
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.39",
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-plugin-baseline.netlify.app/",
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",
@@ -1,4 +0,0 @@
1
- // Filters barrel.
2
- export { markdownFilter } from './markdown.js';
3
- export { relatedPostsFilter } from './related-posts.js';
4
- export { isStringFilter } from './isString.js';
@@ -1,6 +0,0 @@
1
- import { registerDateGlobal } from './date.js';
2
-
3
- /** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
4
- export function registerGlobals(eleventyConfig) {
5
- registerDateGlobal(eleventyConfig);
6
- }
package/core/logging.js DELETED
@@ -1,32 +0,0 @@
1
- import chalk from 'kleur';
2
-
3
- /**
4
- * @typedef {Object} BaselineLogger
5
- * @property {(...args: unknown[]) => void} info Verbose-only.
6
- * @property {(...args: unknown[]) => void} warn Always visible.
7
- * @property {(...args: unknown[]) => void} error Always visible.
8
- */
9
-
10
- /**
11
- * Create a namespaced logger. Prefix is `[baseline]` at plugin root and
12
- * `[baseline:<namespace>]` inside modules. `info` is gated behind `verbose`;
13
- * `warn` and `error` always emit.
14
- *
15
- * @param {string | null | undefined} namespace
16
- * @param {{ verbose?: boolean }} [options]
17
- * @returns {BaselineLogger}
18
- */
19
- export function createLogger(namespace, { verbose = false } = {}) {
20
- const label = namespace ? `[baseline/${namespace}]` : '[baseline]';
21
- return {
22
- info: (...args) => {
23
- if (verbose) console.log(chalk.gray(label), ...args);
24
- },
25
- warn: (...args) => {
26
- console.warn(chalk.yellow().bold(label), ...args);
27
- },
28
- error: (...args) => {
29
- console.error(chalk.red().bold(label), ...args);
30
- }
31
- };
32
- }
@@ -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
- }
@@ -1,2 +0,0 @@
1
- // Shortcodes barrel.
2
- export { imageShortcode } from './image.js';
@@ -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