@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.
- 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/index.js +80 -0
- 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} +6 -6
- 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 +52 -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/types.js +1 -1
- 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 +35 -2
- package/modules/assets/processors/postcss-process.js +36 -2
- package/modules/head/drivers/capo-adapter.js +26 -4
- package/modules/head/drivers/posthtml-head-elements.js +2 -4
- 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/logging.js +0 -32
- 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
|
@@ -10,39 +10,42 @@ const __dirname = path.dirname(__filename);
|
|
|
10
10
|
/**
|
|
11
11
|
* Navigator (module)
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
|
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
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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 `
|
|
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,
|
|
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
|
-
*
|
|
45
|
-
* `_snapshot` + virtual page →
|
|
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('.')}
|
|
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 =
|
|
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
|
|
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
|
-
{{-
|
|
44
|
+
{{- _pageContext | _inspect({ depth: null }) -}}
|
|
45
45
|
</pre>
|
|
46
46
|
</details>
|
|
47
47
|
{% for key, value in _runtime() %}
|
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/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
|
-
}
|
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
|