@apleasantview/eleventy-plugin-baseline 0.1.0-next.33 → 0.1.0-next.39

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 (58) hide show
  1. package/README.md +48 -23
  2. package/core/content-map-store.js +51 -0
  3. package/core/filters/index.js +4 -0
  4. package/core/filters/isString.js +1 -1
  5. package/core/filters/related-posts.js +1 -1
  6. package/core/global-functions/index.js +6 -0
  7. package/core/logging.js +25 -25
  8. package/core/page-context.js +310 -0
  9. package/core/registry.js +110 -0
  10. package/core/schema.js +37 -0
  11. package/core/shortcodes/image.js +8 -3
  12. package/core/shortcodes/index.js +2 -0
  13. package/core/slug-index.js +61 -0
  14. package/core/translation-map-store.js +46 -0
  15. package/core/types.js +73 -0
  16. package/core/utils/helpers.js +75 -0
  17. package/core/utils/pick.js +7 -0
  18. package/core/virtual-dir.js +111 -0
  19. package/core/wikilinks.js +152 -0
  20. package/index.js +364 -0
  21. package/modules/assets/index.js +162 -0
  22. package/modules/{assets-esbuild/process.js → assets/processors/esbuild-process.js} +3 -1
  23. package/modules/{assets-postcss/process.js → assets/processors/postcss-process.js} +5 -2
  24. package/modules/assets/schema.js +14 -0
  25. package/modules/head/drivers/capo-adapter.js +72 -0
  26. package/modules/head/drivers/posthtml-head-elements.js +140 -0
  27. package/modules/head/index.js +106 -0
  28. package/modules/head/schema.js +42 -0
  29. package/modules/head/utils/alternates.js +11 -0
  30. package/modules/head/utils/dedupe.js +47 -0
  31. package/modules/multilang/index.js +149 -0
  32. package/modules/navigator/index.js +140 -0
  33. package/modules/navigator/schema.js +13 -0
  34. package/modules/{navigator-core → navigator}/templates/navigator-core.html +10 -4
  35. package/{core → modules/navigator/utils}/debug.js +7 -1
  36. package/modules/sitemap/index.js +121 -0
  37. package/modules/{sitemap-core → sitemap}/templates/sitemap-core.html +2 -2
  38. package/modules/{sitemap-core → sitemap}/templates/sitemap-index.html +2 -2
  39. package/modules.js +6 -0
  40. package/package.json +15 -6
  41. package/core/filters.js +0 -9
  42. package/core/globals.js +0 -6
  43. package/core/helpers.js +0 -36
  44. package/core/modules.js +0 -18
  45. package/core/shortcodes.js +0 -3
  46. package/eleventy.config.js +0 -169
  47. package/modules/assets-core/plugins/assets-core.js +0 -197
  48. package/modules/head-core/drivers/posthtml-head-elements.js +0 -127
  49. package/modules/head-core/plugins/head-core.js +0 -75
  50. package/modules/head-core/utils/head-utils.js +0 -249
  51. package/modules/multilang-core/plugins/multilang-core.js +0 -118
  52. package/modules/navigator-core/plugins/navigator-core.js +0 -57
  53. package/modules/sitemap-core/plugins/sitemap-core.js +0 -88
  54. /package/core/{globals → global-functions}/date.js +0 -0
  55. /package/modules/{assets-postcss/fallback → assets/configs}/postcss.config.js +0 -0
  56. /package/modules/{multilang-core → multilang}/filters/i18n-default-translation.js +0 -0
  57. /package/modules/{multilang-core → multilang}/filters/i18n-translation-in.js +0 -0
  58. /package/modules/{multilang-core → multilang}/filters/i18n-translations-for.js +0 -0
@@ -1,127 +0,0 @@
1
- // Based on posthtml-head-elements (MIT License).
2
- // Original: https://github.com/posthtml/posthtml-head-elements
3
- // Adapted for Baseline head-core.
4
-
5
- import { createRequire } from 'node:module';
6
-
7
- const require = createRequire(import.meta.url);
8
-
9
- function nonString(type, attrsArr) {
10
- return attrsArr.map(function (attrs) {
11
- return { tag: type, attrs: attrs };
12
- });
13
- }
14
-
15
- function nonArray(type, content) {
16
- return { tag: type, content: [content] };
17
- }
18
-
19
- function findElmType(type, objectData) {
20
- const elementType = {
21
- meta: function () {
22
- if (Array.isArray(objectData)) {
23
- return nonString(type, objectData);
24
- } else {
25
- console.warn('posthtml-head-elements: Please use the correct syntax for a meta element');
26
- }
27
- },
28
- title: function () {
29
- if (typeof objectData === 'string') {
30
- return nonArray('title', objectData);
31
- } else {
32
- console.warn('posthtml-head-elements: Please use the correct syntax for a title element');
33
- }
34
- },
35
- link: function () {
36
- if (Array.isArray(objectData)) {
37
- return nonString(type, objectData);
38
- } else {
39
- console.warn('posthtml-head-elements: Please use the correct syntax for a link element');
40
- }
41
- },
42
- linkCanonical: function () {
43
- if (Array.isArray(objectData)) {
44
- return nonString('link', objectData);
45
- } else {
46
- console.warn('posthtml-head-elements: Please use the correct syntax for a linkCanonical element');
47
- }
48
- },
49
- script: function () {
50
- if (Array.isArray(objectData)) {
51
- return objectData.map(function (entry) {
52
- const { content, ...attrs } = entry || {};
53
- return content !== undefined ? { tag: 'script', attrs, content: [content] } : { tag: 'script', attrs };
54
- });
55
- } else {
56
- console.warn('posthtml-head-elements: Please use the correct syntax for a script element');
57
- }
58
- },
59
- style: function () {
60
- if (Array.isArray(objectData)) {
61
- return objectData.map(function (entry) {
62
- const { content, ...attrs } = entry || {};
63
- return content !== undefined ? { tag: 'style', attrs, content: [content] } : { tag: 'style', attrs };
64
- });
65
- } else {
66
- console.warn('posthtml-head-elements: Please use the correct syntax for a style element');
67
- }
68
- },
69
- base: function () {
70
- if (Array.isArray(objectData)) {
71
- return nonString(type, objectData);
72
- } else {
73
- console.warn('posthtml-head-elements: Please use the correct syntax for a base element');
74
- }
75
- },
76
- default: function () {
77
- console.warn('posthtml-head-elements: Please make sure the HTML head type is correct');
78
- }
79
- };
80
-
81
- if (type.indexOf('_') !== -1) {
82
- type = type.slice(0, type.indexOf('_'));
83
- }
84
-
85
- return elementType[type]() || elementType['default']();
86
- }
87
-
88
- function buildNewTree(headElements, EOL) {
89
- const newHeadElements = [];
90
-
91
- Object.keys(headElements).forEach(function (value) {
92
- newHeadElements.push(findElmType(value, headElements[value]));
93
- });
94
-
95
- function cnct(arr) {
96
- return Array.prototype.concat.apply([], arr);
97
- }
98
-
99
- return cnct(
100
- cnct(newHeadElements).map(function (elem) {
101
- return [elem, EOL];
102
- })
103
- );
104
- }
105
-
106
- export default function (options) {
107
- options = options || {};
108
- options.headElementsTag = options.headElementsTag || 'posthtml-head-elements';
109
-
110
- if (!options.headElements) {
111
- console.warn(
112
- "posthtml-head-elements: Don't forget to add a link to the JSON file containing the head elements to insert"
113
- );
114
- }
115
- const jsonOne = typeof options.headElements !== 'string' ? options.headElements : require(options.headElements);
116
-
117
- return function posthtmlHeadElements(tree) {
118
- tree.match({ tag: options.headElementsTag }, function () {
119
- return {
120
- tag: false, // delete this node, safe content
121
- content: buildNewTree(jsonOne, options.EOL || '\n')
122
- };
123
- });
124
-
125
- return tree;
126
- };
127
- }
@@ -1,75 +0,0 @@
1
- import headElements from '../drivers/posthtml-head-elements.js';
2
- import { getVerbose, logIfVerbose } from '../../../core/logging.js';
3
- import { buildHead } from '../utils/head-utils.js';
4
-
5
- /**
6
- * eleventy-plugin-head-core
7
- *
8
- * Manages the <head> for every page. Merges site-level defaults, page-level
9
- * overrides, and computed values (canonical URL, open graph, structured data)
10
- * into a single head spec, then injects the result into HTML via a PostHTML
11
- * transform. Pages control their head through a `head` data key.
12
- *
13
- * Depends on: core/logging, head-core/utils/head-utils, head-core/drivers/posthtml-head-elements.
14
- * No cross-module dependencies.
15
- */
16
- /** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
17
- export default function headCore(eleventyConfig, options = {}) {
18
- const verbose = getVerbose(eleventyConfig) || options.verbose || false;
19
-
20
- // Internal options — not part of the public API.
21
- const userKey = options.dirKey || 'head';
22
- const headElementsTag = options.headElementsTag || 'baseline-head';
23
- const eol = options.EOL || '\n';
24
- const pathPrefix = options.pathPrefix ?? eleventyConfig?.pathPrefix ?? '';
25
- const siteUrl = options.siteUrl;
26
-
27
- // Cache the content map so canonical URLs can resolve inputPath → URL.
28
- // Updated each build when Eleventy emits the contentMap event.
29
- let cachedContentMap = {};
30
- eleventyConfig.on('eleventy.contentMap', ({ inputPathToUrl, urlToInputPath }) => {
31
- cachedContentMap = { inputPathToUrl, urlToInputPath };
32
- });
33
-
34
- // Computed global data: build the head spec for every page through the
35
- // data cascade. Templates access the result via `page.head`.
36
- eleventyConfig.addGlobalData('eleventyComputed.page.head', () => {
37
- return (data) =>
38
- buildHead(data, {
39
- userKey,
40
- siteUrl,
41
- pathPrefix,
42
- contentMap: cachedContentMap,
43
- pageUrlOverride: data?.page?.url,
44
- verbose
45
- });
46
- });
47
-
48
- // HTML transform: inject the head spec into the document <head> using
49
- // PostHTML. Replaces the <baseline-head> placeholder tag with real elements.
50
- // Falls back to building the spec from context if page.head isn't available.
51
- eleventyConfig.htmlTransformer.addPosthtmlPlugin('html', function (context) {
52
- logIfVerbose(verbose, 'head-core: injecting head elements for', context?.page?.inputPath || context?.outputPath);
53
-
54
- const headElementsSpec =
55
- context?.page?.head ||
56
- buildHead(context, {
57
- userKey,
58
- siteUrl,
59
- pathPrefix,
60
- contentMap: cachedContentMap,
61
- pageUrlOverride: context?.page?.url,
62
- verbose
63
- });
64
-
65
- const plugin = headElements({
66
- headElements: headElementsSpec,
67
- headElementsTag,
68
- EOL: eol
69
- });
70
-
71
- return async function asyncHead(tree) {
72
- return plugin(tree);
73
- };
74
- });
75
- }
@@ -1,249 +0,0 @@
1
- import { Merge, TemplatePath } from '@11ty/eleventy-utils';
2
-
3
- /**
4
- * Return the first value that is neither undefined nor null.
5
- * @param {...*} values
6
- * @returns {*}
7
- */
8
- const pick = (...values) => values.find((v) => v !== undefined && v !== null);
9
-
10
- /**
11
- * Normalize a path prefix to match Eleventy's URL behavior.
12
- * Returns empty string for root ('/'), otherwise the normalized prefix.
13
- * @param {string} [pathPrefix='']
14
- * @returns {string}
15
- */
16
- const normalizePathPrefix = (pathPrefix = '') => {
17
- const normalized = TemplatePath.normalizeUrlPath('/', pathPrefix);
18
- return normalized === '/' ? '' : normalized;
19
- };
20
-
21
- /**
22
- * Test whether a URL is absolute (has a scheme or is protocol-relative).
23
- * @param {string} [url='']
24
- * @returns {boolean}
25
- */
26
- const isAbsoluteUrl = (url = '') => /^[a-z][a-z\d+\-.]*:\/\//i.test(url) || url.startsWith('//');
27
-
28
- /**
29
- * Build an absolute URL from siteUrl + pathPrefix + relative URL.
30
- * Returns the input unchanged if already absolute or empty.
31
- * @param {string} siteUrl - Site root (e.g. 'https://example.com').
32
- * @param {string} pathPrefix - Eleventy path prefix.
33
- * @param {string} url - The URL to resolve.
34
- * @returns {string}
35
- */
36
- const absoluteUrl = (siteUrl, pathPrefix, url) => {
37
- if (!url) return url;
38
- if (isAbsoluteUrl(url)) return url;
39
- const prefix = normalizePathPrefix(pathPrefix);
40
- const joined = TemplatePath.normalizeUrlPath(prefix || '/', url);
41
- return siteUrl ? `${siteUrl.replace(/\/+$/, '')}${joined}` : joined;
42
- };
43
-
44
- /**
45
- * Merge site defaults, user overrides, and computed values into a raw head object.
46
- * The result contains all head sections (meta, link, OG, twitter, etc.) before
47
- * deduplication and flattening.
48
- * @param {Object} site - Site-level data (site.yaml / site.json).
49
- * @param {Object} user - Page-level head overrides (the `head` data key).
50
- * @param {Object} page - Eleventy page object.
51
- * @param {string} title - Resolved page title.
52
- * @param {string} description - Resolved description.
53
- * @param {boolean} noindex - Whether to set noindex/nofollow.
54
- * @param {string} url - Canonical URL.
55
- * @returns {Object} Merged head object.
56
- */
57
- const mergeBaseHead = (site, user, page, title, description, noindex, url) => {
58
- return Merge(
59
- {},
60
- {
61
- title,
62
- meta: [
63
- { charset: 'UTF-8' },
64
- { name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
65
- { name: 'description', content: description },
66
- { name: 'robots', content: noindex ? 'noindex, nofollow' : 'index, follow' }
67
- ],
68
- link: [],
69
- script: [],
70
- style: [],
71
- hreflang: [],
72
- openGraph: {
73
- 'og:title': title,
74
- 'og:description': description,
75
- 'og:type': 'website',
76
- 'og:url': url || '',
77
- 'og:image': ''
78
- },
79
- twitter: {
80
- 'twitter:card': 'summary_large_image',
81
- 'twitter:title': title,
82
- 'twitter:description': description,
83
- 'twitter:image': ''
84
- },
85
- miscMeta: [],
86
- structuredData: null
87
- },
88
- user
89
- );
90
- };
91
-
92
- /**
93
- * Resolve the canonical URL for a page. Uses an explicit canonical from head
94
- * data if present, otherwise derives it from the page URL or content map.
95
- * @param {Object} head - Head data (may contain a canonical property).
96
- * @param {Object} page - Eleventy page object.
97
- * @param {Object} contentMap - Cached inputPathToUrl / urlToInputPath maps.
98
- * @param {Object} [env] - Environment options (siteUrl, pathPrefix, verbose).
99
- * @returns {string|undefined} Absolute canonical URL, or undefined if unresolvable.
100
- */
101
- const resolveCanonical = (head, page, contentMap, env = {}) => {
102
- const { siteUrl, pathPrefix = '', pageUrlOverride, verbose } = env;
103
- const explicit = pick(head.canonical);
104
- if (explicit) {
105
- if (!siteUrl && verbose) {
106
- console.warn('[baseline] site.url is missing; canonical will be relative.');
107
- }
108
- return absoluteUrl(siteUrl, pathPrefix, explicit);
109
- }
110
-
111
- const url = pick(pageUrlOverride, page?.url, page?.inputPath && contentMap?.inputPathToUrl?.[page.inputPath]?.[0]);
112
- if (!url) return undefined;
113
-
114
- if (!siteUrl && verbose) {
115
- console.warn('[baseline] site.url is missing; canonical will be relative.');
116
- }
117
-
118
- return absoluteUrl(siteUrl, pathPrefix, url);
119
- };
120
-
121
- /**
122
- * Deduplicate meta tags. Last-wins by key (charset, name, property, http-equiv).
123
- * Preserves insertion order after dedup.
124
- * @param {Array<Object>} [arr=[]] - Array of meta tag objects.
125
- * @returns {Array<Object>}
126
- */
127
- const dedupeMeta = (arr = []) => {
128
- const seen = new Set();
129
- const out = [];
130
- for (let i = arr.length - 1; i >= 0; i--) {
131
- const m = arr[i];
132
- const key = m.charset
133
- ? 'charset'
134
- : m.name
135
- ? `name:${m.name}`
136
- : m.property
137
- ? `prop:${m.property}`
138
- : m['http-equiv']
139
- ? `http:${m['http-equiv']}`
140
- : null;
141
- if (!key || seen.has(key)) continue;
142
- seen.add(key);
143
- out.push(m);
144
- }
145
- return out.reverse();
146
- };
147
-
148
- /**
149
- * Deduplicate link tags by rel+href. Last-wins, preserves insertion order.
150
- * @param {Array<Object>} [links=[]] - Array of link tag objects.
151
- * @returns {Array<Object>}
152
- */
153
- const dedupeLink = (links = []) => {
154
- const seen = new Set();
155
- const out = [];
156
- for (let i = links.length - 1; i >= 0; i--) {
157
- const l = links[i];
158
- const key = l.rel && l.href ? `rel:${l.rel}|${l.href}` : null;
159
- if (!key || seen.has(key)) continue;
160
- seen.add(key);
161
- out.push(l);
162
- }
163
- return out.reverse();
164
- };
165
-
166
- /**
167
- * Flatten a merged head object into the shape posthtml-head-elements expects.
168
- * Deduplicates meta and link tags, separates social meta, and prepends
169
- * structured data as a JSON-LD script.
170
- * @param {Object} [head={}] - Merged head object from mergeBaseHead.
171
- * @param {string} canonical - Resolved canonical URL.
172
- * @returns {Object} Flat head spec keyed by element type (meta, title, link, script, etc.).
173
- */
174
- const flattenHead = (head = {}, canonical) => {
175
- const baseMeta = dedupeMeta([...(head.meta || []), ...(head.miscMeta || [])]);
176
-
177
- const socialMeta = dedupeMeta([
178
- ...(head.openGraph
179
- ? Object.entries(head.openGraph)
180
- .filter(([, v]) => v)
181
- .map(([k, v]) => ({ property: k, content: v }))
182
- : []),
183
- ...(head.twitter
184
- ? Object.entries(head.twitter)
185
- .filter(([, v]) => v)
186
- .map(([k, v]) => ({ name: k, content: v }))
187
- : [])
188
- ]);
189
-
190
- const style = [...(head.style || [])];
191
-
192
- const linkCanonical = canonical ? [{ rel: 'canonical', href: canonical }] : [];
193
- const link = dedupeLink([...(head.link || []), ...(head.hreflang || [])].filter(Boolean));
194
-
195
- const script = [...(head.script || [])];
196
- if (head.structuredData) {
197
- script.unshift({
198
- type: 'application/ld+json',
199
- content: JSON.stringify(head.structuredData)
200
- });
201
- }
202
-
203
- // Key order matters for posthtml-head-elements.
204
- return {
205
- meta: baseMeta,
206
- title: head.title || '',
207
- linkCanonical,
208
- style,
209
- link,
210
- script,
211
- meta_social: socialMeta
212
- };
213
- };
214
-
215
- /**
216
- * Build the complete head spec for a page. This is the main entry point —
217
- * resolves title, description, canonical, merges everything, and flattens.
218
- * Called from both the computed global data and the PostHTML transform fallback.
219
- * @param {Object} [data={}] - Full Eleventy data cascade for the page.
220
- * @param {Object} [env={}] - Environment options (userKey, contentMap, siteUrl, pathPrefix, verbose).
221
- * @returns {Object} Flat head spec ready for posthtml-head-elements.
222
- */
223
- const buildHead = (data = {}, env = {}) => {
224
- const { userKey = 'head', contentMap = {}, siteUrl, pathPrefix } = env;
225
- const site = data.site || {};
226
- const user = userKey ? data[userKey] || {} : {};
227
- const page = data.page || {};
228
- const resolvedSiteUrl =
229
- siteUrl || site.url || process.env.URL || process.env.DEPLOY_URL || process.env.DEPLOY_PRIME_URL;
230
-
231
- const siteTitle = site.title || '';
232
- const pageTitle = pick(data.title, user.title, site.title, '');
233
- const title =
234
- siteTitle && pageTitle && siteTitle !== pageTitle ? `${pageTitle} | ${siteTitle}` : pageTitle || siteTitle || '';
235
-
236
- const description = pick(data.description, user.description, site.tagline, '');
237
- const noindex = pick(data.noindex, page.noindex, user.noindex, site.noindex, false);
238
-
239
- const canonical = resolveCanonical(
240
- { canonical: absoluteUrl(resolvedSiteUrl, pathPrefix, user.canonical) },
241
- page,
242
- contentMap,
243
- { ...env, siteUrl: resolvedSiteUrl, verbose: env.verbose }
244
- );
245
- const merged = mergeBaseHead(site, user, page, title, description, noindex, canonical);
246
- return flattenHead(merged, canonical);
247
- };
248
-
249
- export { pick, resolveCanonical, flattenHead, buildHead, absoluteUrl };
@@ -1,118 +0,0 @@
1
- import { I18nPlugin } from '@11ty/eleventy';
2
- import { DeepCopy } from '@11ty/eleventy-utils';
3
- import i18nTranslationsFor from '../filters/i18n-translations-for.js';
4
- import i18nTranslationIn from '../filters/i18n-translation-in.js';
5
- import i18nDefaultTranslation from '../filters/i18n-default-translation.js';
6
-
7
- /**
8
- * eleventy-plugin-multilang-core
9
- *
10
- * Language infrastructure for multilingual sites. Normalizes language metadata,
11
- * builds a translations map keyed by translationKey + lang, and exposes
12
- * relational filters for cross-language lookups. Wraps Eleventy's built-in
13
- * I18nPlugin with stricter language validation.
14
- *
15
- * Depends on: Eleventy I18nPlugin (built-in), @11ty/eleventy-utils (DeepCopy).
16
- * No cross-module dependencies. Sitemap-core receives language config via
17
- * options at registration time, not through imports.
18
- *
19
- * Options:
20
- * - defaultLanguage (string, default 'en'): fallback language code.
21
- * - languages (array|object): allowed languages. Pages with unlisted langs are skipped.
22
- * - verbose (boolean, default false): warn on unknown language codes.
23
- *
24
- * @param { import("@11ty/eleventy/src/UserConfig.js").default } eleventyConfig
25
- */
26
- export default function multilangCore(eleventyConfig, options = {}) {
27
- const userOptions = {
28
- defaultLanguage: 'en',
29
- languages: [],
30
- verbose: false,
31
- ...options
32
- };
33
-
34
- // Register Eleventy's built-in I18nPlugin for locale-aware URL resolution.
35
- eleventyConfig.addPlugin(I18nPlugin, {
36
- defaultLanguage: userOptions.defaultLanguage,
37
- errorMode: 'allow-fallback'
38
- });
39
-
40
- // Build a set of allowed language codes for validation during collection building.
41
- const normalizeLang = (lang) => (lang || '').toLowerCase().trim();
42
- const allowedLanguages = new Set(
43
- Array.isArray(userOptions.languages)
44
- ? userOptions.languages.map(normalizeLang)
45
- : Object.keys(userOptions.languages || {}).map(normalizeLang)
46
- );
47
-
48
- // Computed locale data: every page gets a page.locale object with its
49
- // resolved lang, translationKey, and whether it's the default language.
50
- eleventyConfig.addGlobalData('eleventyComputed.page.locale', () => {
51
- return (data) => {
52
- const lang = normalizeLang(data.lang || data.language || userOptions.defaultLanguage);
53
- const translationKey = data.translationKey;
54
- const isDefaultLang = lang === normalizeLang(userOptions.defaultLanguage);
55
-
56
- return {
57
- translationKey,
58
- lang,
59
- isDefaultLang
60
- };
61
- };
62
- });
63
-
64
- // Build both the map (keyed by translationKey → lang) and the flat list.
65
- // Shared logic for both collections — called once per collection registration.
66
- const buildTranslations = (collection) => {
67
- const map = {};
68
- const list = [];
69
-
70
- for (const page of collection.getAll()) {
71
- const translationKey = page.data.translationKey;
72
- if (!translationKey) continue;
73
-
74
- const lang = page.data.lang || page.data.language || userOptions.defaultLanguage;
75
- if (!lang) continue;
76
-
77
- if (allowedLanguages.size && !allowedLanguages.has(lang)) {
78
- if (userOptions.verbose) {
79
- console.warn(`[baseline:multilang-core] Unknown lang "${lang}" in ${page.inputPath}`);
80
- }
81
- continue;
82
- }
83
-
84
- const locale = { locale: { translationKey, lang, isDefaultLang: lang === userOptions.defaultLanguage } };
85
- const safeCopy = DeepCopy(page, locale);
86
- list.push(safeCopy);
87
-
88
- if (!map[translationKey]) map[translationKey] = {};
89
- map[translationKey][lang] = {
90
- title: page.data.title,
91
- url: page.url,
92
- lang,
93
- isDefaultLang: lang === userOptions.defaultLanguage,
94
- data: page.data
95
- };
96
- }
97
-
98
- return { map, list };
99
- };
100
-
101
- // --- Collections ---
102
-
103
- // Map form: translationsMap[translationKey][lang] → page metadata.
104
- eleventyConfig.addCollection('translationsMap', (collection) => {
105
- return buildTranslations(collection).map;
106
- });
107
-
108
- // Flat list: all translatable pages with locale data attached.
109
- eleventyConfig.addCollection('translations', (collection) => {
110
- return buildTranslations(collection).list;
111
- });
112
-
113
- // --- Filters ---
114
- // Relational helpers for cross-language lookups in templates.
115
- eleventyConfig.addFilter('i18nTranslationsFor', i18nTranslationsFor);
116
- eleventyConfig.addFilter('i18nTranslationIn', i18nTranslationIn);
117
- eleventyConfig.addFilter('i18nDefaultTranslation', i18nDefaultTranslation);
118
- }
@@ -1,57 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(__filename);
7
-
8
- /**
9
- * eleventy-plugin-navigator-core
10
- *
11
- * Debug tooling. Exposes the full Nunjucks environment and template context
12
- * as globals so templates can inspect what data is available at render time.
13
- * Optionally registers a virtual /navigator-core.html page that dumps
14
- * everything in a readable format.
15
- *
16
- * No dependencies on other modules or core utilities. Standalone.
17
- *
18
- * Options:
19
- * - enableNavigatorTemplate (boolean|[boolean, number]): register the debug page.
20
- * Pass [true, depth] to control inspector depth (default 2).
21
- */
22
- /** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */
23
- export default function navigatorCore(eleventyConfig, options = {}) {
24
- const raw = options.enableNavigatorTemplate;
25
- const [enableNavigatorTemplate, inspectorDepth] = Array.isArray(raw) ? [raw[0], raw[1]] : [raw, undefined];
26
-
27
- const userOptions = {
28
- ...options,
29
- enableNavigatorTemplate: enableNavigatorTemplate ?? false,
30
- inspectorDepth: inspectorDepth ?? 2
31
- };
32
-
33
- // --- Globals ---
34
- // _navigator: the full Nunjucks runtime environment (this).
35
- // _context: the template context object (this.ctx) — what templates actually see.
36
- eleventyConfig.addNunjucksGlobal('_navigator', function () {
37
- return this;
38
- });
39
- eleventyConfig.addNunjucksGlobal('_context', function () {
40
- return this.ctx;
41
- });
42
-
43
- // --- Virtual debug template ---
44
- if (userOptions.enableNavigatorTemplate) {
45
- // Read synchronously — Nunjucks virtual template registration is sync-only.
46
- const templatePath = path.join(__dirname, '../templates/navigator-core.html');
47
- const virtualTemplateContent = fs.readFileSync(templatePath, 'utf-8');
48
- eleventyConfig.addTemplate('navigator-core.html', virtualTemplateContent, {
49
- permalink: '/navigator-core.html',
50
- title: 'Navigator Core',
51
- description: '',
52
- layout: null,
53
- eleventyExcludeFromCollections: true,
54
- inspectorDepth: userOptions.inspectorDepth
55
- });
56
- }
57
- }