summaryrefslogtreecommitdiff
path: root/node_modules/@11ty/eleventy/src/Plugins
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@11ty/eleventy/src/Plugins')
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js160
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/HtmlRelativeCopyPlugin.js52
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js317
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js110
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js191
-rwxr-xr-xnode_modules/@11ty/eleventy/src/Plugins/Pagination.js379
-rw-r--r--node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js520
7 files changed, 1729 insertions, 0 deletions
diff --git a/node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js b/node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js
new file mode 100644
index 0000000..304c0a2
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/HtmlBasePlugin.js
@@ -0,0 +1,160 @@
+import { DeepCopy } from "@11ty/eleventy-utils";
+import urlFilter from "../Filters/Url.js";
+import PathPrefixer from "../Util/PathPrefixer.js";
+import { HtmlTransformer } from "../Util/HtmlTransformer.js";
+import isValidUrl from "../Util/ValidUrl.js";
+
+function addPathPrefixToUrl(url, pathPrefix, base) {
+ let u;
+ if (base) {
+ u = new URL(url, base);
+ } else {
+ u = new URL(url);
+ }
+
+ // Add pathPrefix **after** url is transformed using base
+ if (pathPrefix) {
+ u.pathname = PathPrefixer.joinUrlParts(pathPrefix, u.pathname);
+ }
+ return u.toString();
+}
+
+// pathprefix is only used when overrideBase is a full URL
+function transformUrl(url, base, opts = {}) {
+ let { pathPrefix, pageUrl, htmlContext } = opts;
+
+ // Warning, this will not work with HtmlTransformer, as we’ll receive "false" (string) here instead of `false` (boolean)
+ if (url === false) {
+ throw new Error(
+ `Invalid url transformed in the HTML \`<base>\` plugin.${url === false ? ` Did you attempt to link to a \`permalink: false\` page?` : ""} Received: ${url}`,
+ );
+ }
+
+ // full URL, return as-is
+ if (isValidUrl(url)) {
+ return url;
+ }
+
+ // Not a full URL, but with a full base URL
+ // e.g. relative urls like "subdir/", "../subdir", "./subdir"
+ if (isValidUrl(base)) {
+ // convert relative paths to absolute path first using pageUrl
+ if (pageUrl && !url.startsWith("/")) {
+ let urlObj = new URL(url, `http://example.com${pageUrl}`);
+ url = urlObj.pathname + (urlObj.hash || "");
+ }
+
+ return addPathPrefixToUrl(url, pathPrefix, base);
+ }
+
+ // Not a full URL, nor a full base URL (call the built-in `url` filter)
+ return urlFilter(url, base);
+}
+
+function eleventyHtmlBasePlugin(eleventyConfig, defaultOptions = {}) {
+ let opts = DeepCopy(
+ {
+ // eleventyConfig.pathPrefix is new in Eleventy 2.0.0-canary.15
+ // `base` can be a directory (for path prefix transformations)
+ // OR a full URL with origin and pathname
+ baseHref: eleventyConfig.pathPrefix,
+
+ extensions: "html",
+ },
+ defaultOptions,
+ );
+
+ // `filters` option to rename filters was removed in 3.0.0-alpha.13
+ // Renaming these would cause issues in other plugins (e.g. RSS)
+ if (opts.filters !== undefined) {
+ throw new Error(
+ "The `filters` option in the HTML Base plugin was removed to prevent future cross-plugin compatibility issues.",
+ );
+ }
+
+ if (opts.baseHref === undefined) {
+ throw new Error("The `baseHref` option is required in the HTML Base plugin.");
+ }
+
+ eleventyConfig.addFilter("addPathPrefixToFullUrl", function (url) {
+ return addPathPrefixToUrl(url, eleventyConfig.pathPrefix);
+ });
+
+ // Apply to one URL
+ eleventyConfig.addFilter(
+ "htmlBaseUrl",
+
+ /** @this {object} */
+ function (url, baseOverride, pageUrlOverride) {
+ let base = baseOverride || opts.baseHref;
+
+ // Do nothing with a default base
+ if (base === "/") {
+ return url;
+ }
+
+ return transformUrl(url, base, {
+ pathPrefix: eleventyConfig.pathPrefix,
+ pageUrl: pageUrlOverride || this.page?.url,
+ });
+ },
+ );
+
+ // Apply to a block of HTML
+ eleventyConfig.addAsyncFilter(
+ "transformWithHtmlBase",
+
+ /** @this {object} */
+ function (content, baseOverride, pageUrlOverride) {
+ let base = baseOverride || opts.baseHref;
+
+ // Do nothing with a default base
+ if (base === "/") {
+ return content;
+ }
+
+ return HtmlTransformer.transformStandalone(content, (url, htmlContext) => {
+ return transformUrl(url.trim(), base, {
+ pathPrefix: eleventyConfig.pathPrefix,
+ pageUrl: pageUrlOverride || this.page?.url,
+ htmlContext,
+ });
+ });
+ },
+ );
+
+ // Apply to all HTML output in your project
+ eleventyConfig.htmlTransformer.addUrlTransform(
+ opts.extensions,
+
+ /** @this {object} */
+ function (urlInMarkup, htmlContext) {
+ // baseHref override is via renderTransforms filter for adding the absolute URL (e.g. https://example.com/pathPrefix/) for RSS/Atom/JSON feeds
+ return transformUrl(urlInMarkup.trim(), this.baseHref || opts.baseHref, {
+ pathPrefix: eleventyConfig.pathPrefix,
+ pageUrl: this.url,
+ htmlContext,
+ });
+ },
+ {
+ priority: -2, // priority is descending, so this runs last (especially after AutoCopy and InputPathToUrl transform)
+ enabled: function (context) {
+ // Enabled when pathPrefix is non-default or via renderTransforms
+ return Boolean(context.baseHref) || opts.baseHref !== "/";
+ },
+ },
+ );
+}
+
+Object.defineProperty(eleventyHtmlBasePlugin, "eleventyPackage", {
+ value: "@11ty/eleventy/html-base-plugin",
+});
+
+Object.defineProperty(eleventyHtmlBasePlugin, "eleventyPluginOptions", {
+ value: {
+ unique: true,
+ },
+});
+
+export default eleventyHtmlBasePlugin;
+export { transformUrl as applyBaseToUrl };
diff --git a/node_modules/@11ty/eleventy/src/Plugins/HtmlRelativeCopyPlugin.js b/node_modules/@11ty/eleventy/src/Plugins/HtmlRelativeCopyPlugin.js
new file mode 100644
index 0000000..ac1391d
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/HtmlRelativeCopyPlugin.js
@@ -0,0 +1,52 @@
+import { HtmlRelativeCopy } from "../Util/HtmlRelativeCopy.js";
+
+// one HtmlRelativeCopy instance per entry
+function init(eleventyConfig, options) {
+ let opts = Object.assign(
+ {
+ extensions: "html",
+ match: false, // can be one glob string or an array of globs
+ paths: [], // directories to also look in for files
+ failOnError: true, // fails when a path matches (via `match`) but not found on file system
+ copyOptions: undefined,
+ },
+ options,
+ );
+
+ let htmlrel = new HtmlRelativeCopy();
+ htmlrel.setUserConfig(eleventyConfig);
+ htmlrel.addMatchingGlob(opts.match);
+ htmlrel.setFailOnError(opts.failOnError);
+ htmlrel.setCopyOptions(opts.copyOptions);
+
+ eleventyConfig.htmlTransformer.addUrlTransform(
+ opts.extensions,
+ function (targetFilepathOrUrl) {
+ // @ts-ignore
+ htmlrel.copy(targetFilepathOrUrl, this.page.inputPath, this.page.outputPath);
+
+ // TODO front matter option for manual copy
+ return targetFilepathOrUrl;
+ },
+ {
+ enabled: () => htmlrel.isEnabled(),
+ // - MUST run after other plugins but BEFORE HtmlBase plugin
+ priority: -1,
+ },
+ );
+
+ htmlrel.addPaths(opts.paths);
+}
+
+function HtmlRelativeCopyPlugin(eleventyConfig) {
+ // Important: if this is empty, no URL transforms are added
+ for (let options of eleventyConfig.passthroughCopiesHtmlRelative) {
+ init(eleventyConfig, options);
+ }
+}
+
+Object.defineProperty(HtmlRelativeCopyPlugin, "eleventyPackage", {
+ value: "@11ty/eleventy/html-relative-copy-plugin",
+});
+
+export { HtmlRelativeCopyPlugin };
diff --git a/node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js b/node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js
new file mode 100644
index 0000000..6f53825
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/I18nPlugin.js
@@ -0,0 +1,317 @@
+import { bcp47Normalize } from "bcp-47-normalize";
+import iso639 from "iso-639-1";
+import { DeepCopy } from "@11ty/eleventy-utils";
+
+// pathPrefix note:
+// When using `locale_url` filter with the `url` filter, `locale_url` must run first like
+// `| locale_url | url`. If you run `| url | locale_url` it won’t match correctly.
+
+// TODO improvement would be to throw an error if `locale_url` finds a url with the
+// path prefix at the beginning? Would need a better way to know `url` has transformed a string
+// rather than just raw comparison.
+// e.g. --pathprefix=/en/ should return `/en/en/` for `/en/index.liquid`
+
+class LangUtils {
+ static getLanguageCodeFromInputPath(filepath) {
+ return (filepath || "").split("/").find((entry) => Comparator.isLangCode(entry));
+ }
+
+ static getLanguageCodeFromUrl(url) {
+ let s = (url || "").split("/");
+ return s.length > 0 && Comparator.isLangCode(s[1]) ? s[1] : "";
+ }
+
+ static swapLanguageCodeNoCheck(str, langCode) {
+ let found = false;
+ return str
+ .split("/")
+ .map((entry) => {
+ // only match the first one
+ if (!found && Comparator.isLangCode(entry)) {
+ found = true;
+ return langCode;
+ }
+ return entry;
+ })
+ .join("/");
+ }
+
+ static swapLanguageCode(str, langCode) {
+ if (!Comparator.isLangCode(langCode)) {
+ return str;
+ }
+
+ return LangUtils.swapLanguageCodeNoCheck(str, langCode);
+ }
+}
+
+class Comparator {
+ // https://en.wikipedia.org/wiki/IETF_language_tag#Relation_to_other_standards
+ // Requires a ISO-639-1 language code at the start (2 characters before the first -)
+ static isLangCode(code) {
+ let [s] = (code || "").split("-");
+ if (!iso639.validate(s)) {
+ return false;
+ }
+ if (!bcp47Normalize(code)) {
+ return false;
+ }
+ return true;
+ }
+
+ static urlHasLangCode(url, code) {
+ if (!Comparator.isLangCode(code)) {
+ return false;
+ }
+
+ return url.split("/").some((entry) => entry === code);
+ }
+}
+
+function normalizeInputPath(inputPath, extensionMap) {
+ if (extensionMap) {
+ return extensionMap.removeTemplateExtension(inputPath);
+ }
+ return inputPath;
+}
+
+/*
+ * Input: {
+ * '/en-us/test/': './test/stubs-i18n/en-us/test.11ty.js',
+ * '/en/test/': './test/stubs-i18n/en/test.liquid',
+ * '/es/test/': './test/stubs-i18n/es/test.njk',
+ * '/non-lang-file/': './test/stubs-i18n/non-lang-file.njk'
+ * }
+ *
+ * Output: {
+ * '/en-us/test/': [ { url: '/en/test/' }, { url: '/es/test/' } ],
+ * '/en/test/': [ { url: '/en-us/test/' }, { url: '/es/test/' } ],
+ * '/es/test/': [ { url: '/en-us/test/' }, { url: '/en/test/' } ]
+ * }
+ */
+function getLocaleUrlsMap(urlToInputPath, extensionMap, options = {}) {
+ let filemap = {};
+
+ for (let url in urlToInputPath) {
+ // Group number comes from Pagination.js
+ let { inputPath: originalFilepath, groupNumber } = urlToInputPath[url];
+ let filepath = normalizeInputPath(originalFilepath, extensionMap);
+ let replaced =
+ LangUtils.swapLanguageCodeNoCheck(filepath, "__11ty_i18n") + `_group:${groupNumber}`;
+
+ if (!filemap[replaced]) {
+ filemap[replaced] = [];
+ }
+
+ let langCode = LangUtils.getLanguageCodeFromInputPath(originalFilepath);
+ if (!langCode) {
+ langCode = LangUtils.getLanguageCodeFromUrl(url);
+ }
+ if (!langCode) {
+ langCode = options.defaultLanguage;
+ }
+
+ if (langCode) {
+ filemap[replaced].push({
+ url,
+ lang: langCode,
+ label: iso639.getNativeName(langCode.split("-")[0]),
+ });
+ } else {
+ filemap[replaced].push({ url });
+ }
+ }
+
+ // Default sorted by lang code
+ for (let key in filemap) {
+ filemap[key].sort(function (a, b) {
+ if (a.lang < b.lang) {
+ return -1;
+ }
+ if (a.lang > b.lang) {
+ return 1;
+ }
+ return 0;
+ });
+ }
+
+ // map of input paths => array of localized urls
+ let urlMap = {};
+ for (let filepath in filemap) {
+ for (let entry of filemap[filepath]) {
+ let url = entry.url;
+ if (!urlMap[url]) {
+ urlMap[url] = filemap[filepath].filter((entry) => {
+ if (entry.lang) {
+ return true;
+ }
+ return entry.url !== url;
+ });
+ }
+ }
+ }
+
+ return urlMap;
+}
+
+function eleventyI18nPlugin(eleventyConfig, opts = {}) {
+ let options = DeepCopy(
+ {
+ defaultLanguage: "",
+ filters: {
+ url: "locale_url",
+ links: "locale_links",
+ },
+ errorMode: "strict", // allow-fallback, never
+ },
+ opts,
+ );
+
+ if (!options.defaultLanguage) {
+ throw new Error(
+ "You must specify a `defaultLanguage` in Eleventy’s Internationalization (I18N) plugin.",
+ );
+ }
+
+ let extensionMap;
+ eleventyConfig.on("eleventy.extensionmap", (map) => {
+ extensionMap = map;
+ });
+
+ let bench = eleventyConfig.benchmarkManager.get("Aggregate");
+ let contentMaps = {};
+ eleventyConfig.on("eleventy.contentMap", function ({ urlToInputPath, inputPathToUrl }) {
+ let b = bench.get("(i18n Plugin) Setting up content map.");
+ b.before();
+ contentMaps.inputPathToUrl = inputPathToUrl;
+ contentMaps.urlToInputPath = urlToInputPath;
+
+ contentMaps.localeUrlsMap = getLocaleUrlsMap(urlToInputPath, extensionMap, options);
+ b.after();
+ });
+
+ eleventyConfig.addGlobalData("eleventyComputed.page.lang", () => {
+ // if addGlobalData receives a function it will execute it immediately,
+ // so we return a nested function for computed data
+ return (data) => {
+ return LangUtils.getLanguageCodeFromUrl(data.page.url) || options.defaultLanguage;
+ };
+ });
+
+ // Normalize a theoretical URL based on the current page’s language
+ // If a non-localized file exists, returns the URL without a language assigned
+ // Fails if no file exists (localized and not localized)
+ eleventyConfig.addFilter(options.filters.url, function (url, langCodeOverride) {
+ let langCode =
+ langCodeOverride ||
+ LangUtils.getLanguageCodeFromUrl(this.page?.url) ||
+ options.defaultLanguage;
+
+ // Already has a language code on it and has a relevant url with the target language code
+ if (
+ contentMaps.localeUrlsMap[url] ||
+ (!url.endsWith("/") && contentMaps.localeUrlsMap[`${url}/`])
+ ) {
+ for (let existingUrlObj of contentMaps.localeUrlsMap[url] ||
+ contentMaps.localeUrlsMap[`${url}/`]) {
+ if (Comparator.urlHasLangCode(existingUrlObj.url, langCode)) {
+ return existingUrlObj.url;
+ }
+ }
+ }
+
+ // Needs the language code prepended to the URL
+ let prependedLangCodeUrl = `/${langCode}${url}`;
+ if (
+ contentMaps.localeUrlsMap[prependedLangCodeUrl] ||
+ (!prependedLangCodeUrl.endsWith("/") && contentMaps.localeUrlsMap[`${prependedLangCodeUrl}/`])
+ ) {
+ return prependedLangCodeUrl;
+ }
+
+ if (
+ contentMaps.urlToInputPath[url] ||
+ (!url.endsWith("/") && contentMaps.urlToInputPath[`${url}/`])
+ ) {
+ // this is not a localized file (independent of a language code)
+ if (options.errorMode === "strict") {
+ throw new Error(
+ `Localized file for URL ${prependedLangCodeUrl} was not found in your project. A non-localized version does exist—are you sure you meant to use the \`${options.filters.url}\` filter for this? You can bypass this error using the \`errorMode\` option in the I18N plugin (current value: "${options.errorMode}").`,
+ );
+ }
+ } else if (options.errorMode === "allow-fallback") {
+ // You’re linking to a localized file that doesn’t exist!
+ throw new Error(
+ `Localized file for URL ${prependedLangCodeUrl} was not found in your project! You will need to add it if you want to link to it using the \`${options.filters.url}\` filter. You can bypass this error using the \`errorMode\` option in the I18N plugin (current value: "${options.errorMode}").`,
+ );
+ }
+
+ return url;
+ });
+
+ // Refactor to use url
+ // Find the links that are localized alternates to the inputPath argument
+ eleventyConfig.addFilter(options.filters.links, function (urlOverride) {
+ let url = urlOverride || this.page?.url;
+ return (contentMaps.localeUrlsMap[url] || []).filter((entry) => {
+ return entry.url !== url;
+ });
+ });
+
+ // Returns a `page`-esque variable for the root default language page
+ // If paginated, returns first result only
+ eleventyConfig.addFilter(
+ "locale_page", // This is not exposed in `options` because it is an Eleventy internals filter (used in get*CollectionItem filters)
+ function (pageOverride, languageCode) {
+ // both args here are optional
+ if (!languageCode) {
+ languageCode = options.defaultLanguage;
+ }
+
+ let page = pageOverride || this.page;
+ let url; // new url
+ if (contentMaps.localeUrlsMap[page.url]) {
+ for (let entry of contentMaps.localeUrlsMap[page.url]) {
+ if (entry.lang === languageCode) {
+ url = entry.url;
+ }
+ }
+ }
+
+ let inputPath = LangUtils.swapLanguageCode(page.inputPath, languageCode);
+
+ if (
+ !url ||
+ !Array.isArray(contentMaps.inputPathToUrl[inputPath]) ||
+ contentMaps.inputPathToUrl[inputPath].length === 0
+ ) {
+ // no internationalized pages found
+ return page;
+ }
+
+ let result = {
+ // // note that the permalink/slug may be different for the localized file!
+ url,
+ inputPath,
+ filePathStem: LangUtils.swapLanguageCode(page.filePathStem, languageCode),
+ // outputPath is omitted here, not necessary for GetCollectionItem.js if url is provided
+ __locale_page_resolved: true,
+ };
+ return result;
+ },
+ );
+}
+
+export { Comparator, LangUtils };
+
+Object.defineProperty(eleventyI18nPlugin, "eleventyPackage", {
+ value: "@11ty/eleventy/i18n-plugin",
+});
+
+Object.defineProperty(eleventyI18nPlugin, "eleventyPluginOptions", {
+ value: {
+ unique: true,
+ },
+});
+
+export default eleventyI18nPlugin;
diff --git a/node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js b/node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js
new file mode 100644
index 0000000..a55a13e
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/IdAttributePlugin.js
@@ -0,0 +1,110 @@
+import matchHelper from "posthtml-match-helper";
+import { decodeHTML } from "entities";
+
+import slugifyFilter from "../Filters/Slugify.js";
+import MemoizeUtil from "../Util/MemoizeFunction.js";
+
+const POSTHTML_PLUGIN_NAME = "11ty/eleventy/id-attribute";
+
+function getTextNodeContent(node) {
+ if (node.attrs?.["eleventy:id-ignore"] === "") {
+ delete node.attrs["eleventy:id-ignore"];
+ return "";
+ }
+ if (!node.content) {
+ return "";
+ }
+
+ return node.content
+ .map((entry) => {
+ if (typeof entry === "string") {
+ return entry;
+ }
+ if (Array.isArray(entry.content)) {
+ return getTextNodeContent(entry);
+ }
+ return "";
+ })
+ .join("");
+}
+
+function IdAttributePlugin(eleventyConfig, options = {}) {
+ if (!options.slugify) {
+ options.slugify = MemoizeUtil(slugifyFilter);
+ }
+ if (!options.selector) {
+ options.selector = "[id],h1,h2,h3,h4,h5,h6";
+ }
+ options.decodeEntities = options.decodeEntities ?? true;
+ options.checkDuplicates = options.checkDuplicates ?? "error";
+
+ eleventyConfig.htmlTransformer.addPosthtmlPlugin(
+ "html",
+ function idAttributePosthtmlPlugin(pluginOptions = {}) {
+ if (typeof options.filter === "function") {
+ if (options.filter(pluginOptions) === false) {
+ return function () {};
+ }
+ }
+
+ return function (tree) {
+ // One per page
+ let conflictCheck = {};
+ // Cache heading nodes for conflict resolution
+ let headingNodes = {};
+
+ tree.match(matchHelper(options.selector), function (node) {
+ if (node.attrs?.id) {
+ let id = node.attrs?.id;
+ if (conflictCheck[id]) {
+ conflictCheck[id]++;
+ if (headingNodes[id]) {
+ // Rename conflicting assigned heading id
+ let newId = `${id}-${conflictCheck[id]}`;
+ headingNodes[newId] = headingNodes[id];
+ headingNodes[newId].attrs.id = newId;
+ delete headingNodes[id];
+ } else if (options.checkDuplicates === "error") {
+ // Existing `id` conflicts with assigned heading id, throw error
+ throw new Error(
+ 'You have more than one HTML `id` attribute using the same value (id="' +
+ id +
+ '") in your template (' +
+ pluginOptions.page.inputPath +
+ "). You can disable this error in the IdAttribute plugin with the `checkDuplicates: false` option.",
+ );
+ }
+ } else {
+ conflictCheck[id] = 1;
+ }
+ } else if (!node.attrs?.id && node.content) {
+ node.attrs = node.attrs || {};
+ let textContent = getTextNodeContent(node);
+ if (options.decodeEntities) {
+ textContent = decodeHTML(textContent);
+ }
+ let id = options.slugify(textContent);
+
+ if (conflictCheck[id]) {
+ conflictCheck[id]++;
+ id = `${id}-${conflictCheck[id]}`;
+ } else {
+ conflictCheck[id] = 1;
+ }
+
+ headingNodes[id] = node;
+ node.attrs.id = id;
+ }
+
+ return node;
+ });
+ };
+ },
+ {
+ // pluginOptions
+ name: POSTHTML_PLUGIN_NAME,
+ },
+ );
+}
+
+export { IdAttributePlugin };
diff --git a/node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js b/node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js
new file mode 100644
index 0000000..aca148b
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/InputPathToUrl.js
@@ -0,0 +1,191 @@
+import path from "node:path";
+import { TemplatePath } from "@11ty/eleventy-utils";
+import isValidUrl from "../Util/ValidUrl.js";
+
+function getValidPath(contentMap, testPath) {
+ // if the path is coming from Markdown, it may be encoded
+ let normalized = TemplatePath.addLeadingDotSlash(decodeURIComponent(testPath));
+
+ // it must exist in the content map to be valid
+ if (contentMap[normalized]) {
+ return normalized;
+ }
+}
+
+function normalizeInputPath(targetInputPath, inputDir, sourceInputPath, contentMap) {
+ // inputDir is optional at the beginning of the developer supplied-path
+
+ // Input directory already on the input path
+ if (TemplatePath.join(targetInputPath).startsWith(TemplatePath.join(inputDir))) {
+ let absolutePath = getValidPath(contentMap, targetInputPath);
+ if (absolutePath) {
+ return absolutePath;
+ }
+ }
+
+ // Relative to project input directory
+ let relativeToInputDir = getValidPath(contentMap, TemplatePath.join(inputDir, targetInputPath));
+ if (relativeToInputDir) {
+ return relativeToInputDir;
+ }
+
+ if (targetInputPath && !path.isAbsolute(targetInputPath)) {
+ // Relative to source file’s input path
+ let sourceInputDir = TemplatePath.getDirFromFilePath(sourceInputPath);
+ let relativeToSourceFile = getValidPath(
+ contentMap,
+ TemplatePath.join(sourceInputDir, targetInputPath),
+ );
+ if (relativeToSourceFile) {
+ return relativeToSourceFile;
+ }
+ }
+
+ // the transform may have sent in a URL so we just return it as-is
+ return targetInputPath;
+}
+
+function parseFilePath(filepath) {
+ if (filepath.startsWith("#") || filepath.startsWith("?")) {
+ return [filepath, ""];
+ }
+
+ try {
+ /* u: URL {
+ href: 'file:///tmpl.njk#anchor',
+ origin: 'null',
+ protocol: 'file:',
+ username: '',
+ password: '',
+ host: '',
+ hostname: '',
+ port: '',
+ pathname: '/tmpl.njk',
+ search: '',
+ searchParams: URLSearchParams {},
+ hash: '#anchor'
+ } */
+
+ // Note that `node:url` -> pathToFileURL creates an absolute path, which we don’t want
+ // URL(`file:#anchor`) gives back a pathname of `/`
+ let u = new URL(`file:${filepath}`);
+ filepath = filepath.replace(u.search, ""); // includes ?
+ filepath = filepath.replace(u.hash, ""); // includes #
+
+ return [
+ // search includes ?, hash includes #
+ u.search + u.hash,
+ filepath,
+ ];
+ } catch (e) {
+ return ["", filepath];
+ }
+}
+
+function FilterPlugin(eleventyConfig) {
+ let contentMap;
+ eleventyConfig.on("eleventy.contentMap", function ({ inputPathToUrl }) {
+ contentMap = inputPathToUrl;
+ });
+
+ eleventyConfig.addFilter("inputPathToUrl", function (targetFilePath) {
+ if (!contentMap) {
+ throw new Error("Internal error: contentMap not available for `inputPathToUrl` filter.");
+ }
+
+ if (isValidUrl(targetFilePath)) {
+ return targetFilePath;
+ }
+
+ let inputDir = eleventyConfig.directories.input;
+ let suffix = "";
+ [suffix, targetFilePath] = parseFilePath(targetFilePath);
+ if (targetFilePath) {
+ targetFilePath = normalizeInputPath(
+ targetFilePath,
+ inputDir,
+ // @ts-ignore
+ this.page.inputPath,
+ contentMap,
+ );
+ }
+
+ let urls = contentMap[targetFilePath];
+ if (!urls || urls.length === 0) {
+ throw new Error(
+ "`inputPathToUrl` filter could not find a matching target for " + targetFilePath,
+ );
+ }
+
+ return `${urls[0]}${suffix}`;
+ });
+}
+
+function TransformPlugin(eleventyConfig, defaultOptions = {}) {
+ let opts = Object.assign(
+ {
+ extensions: "html",
+ },
+ defaultOptions,
+ );
+
+ let contentMap = null;
+ eleventyConfig.on("eleventy.contentMap", function ({ inputPathToUrl }) {
+ contentMap = inputPathToUrl;
+ });
+
+ eleventyConfig.htmlTransformer.addUrlTransform(opts.extensions, function (targetFilepathOrUrl) {
+ if (!contentMap) {
+ throw new Error("Internal error: contentMap not available for the `pathToUrl` Transform.");
+ }
+ if (isValidUrl(targetFilepathOrUrl)) {
+ return targetFilepathOrUrl;
+ }
+
+ let inputDir = eleventyConfig.directories.input;
+
+ let suffix = "";
+ [suffix, targetFilepathOrUrl] = parseFilePath(targetFilepathOrUrl);
+ if (targetFilepathOrUrl) {
+ targetFilepathOrUrl = normalizeInputPath(
+ targetFilepathOrUrl,
+ inputDir,
+ // @ts-ignore
+ this.page.inputPath,
+ contentMap,
+ );
+ }
+
+ let urls = contentMap[targetFilepathOrUrl];
+ if (!targetFilepathOrUrl || !urls || urls.length === 0) {
+ // fallback, transforms don’t error on missing paths (though the pathToUrl filter does)
+ return `${targetFilepathOrUrl}${suffix}`;
+ }
+
+ return `${urls[0]}${suffix}`;
+ });
+}
+
+Object.defineProperty(FilterPlugin, "eleventyPackage", {
+ value: "@11ty/eleventy/inputpath-to-url-filter-plugin",
+});
+
+Object.defineProperty(FilterPlugin, "eleventyPluginOptions", {
+ value: {
+ unique: true,
+ },
+});
+
+Object.defineProperty(TransformPlugin, "eleventyPackage", {
+ value: "@11ty/eleventy/inputpath-to-url-transform-plugin",
+});
+
+Object.defineProperty(TransformPlugin, "eleventyPluginOptions", {
+ value: {
+ unique: true,
+ },
+});
+
+export default TransformPlugin;
+
+export { FilterPlugin, TransformPlugin };
diff --git a/node_modules/@11ty/eleventy/src/Plugins/Pagination.js b/node_modules/@11ty/eleventy/src/Plugins/Pagination.js
new file mode 100755
index 0000000..8e5b1de
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/Pagination.js
@@ -0,0 +1,379 @@
+import { isPlainObject } from "@11ty/eleventy-utils";
+import lodash from "@11ty/lodash-custom";
+import { DeepCopy } from "@11ty/eleventy-utils";
+
+import EleventyBaseError from "../Errors/EleventyBaseError.js";
+import { ProxyWrap } from "../Util/Objects/ProxyWrap.js";
+// import { DeepFreeze } from "../Util/Objects/DeepFreeze.js";
+import TemplateData from "../Data/TemplateData.js";
+
+const { set: lodashSet, get: lodashGet, chunk: lodashChunk } = lodash;
+
+class PaginationConfigError extends EleventyBaseError {}
+class PaginationError extends EleventyBaseError {}
+
+class Pagination {
+ constructor(tmpl, data, config) {
+ if (!config) {
+ throw new PaginationConfigError("Expected `config` argument to Pagination class.");
+ }
+
+ this.config = config;
+
+ this.setTemplate(tmpl);
+ this.setData(data);
+ }
+
+ get inputPathForErrorMessages() {
+ if (this.template) {
+ return ` (${this.template.inputPath})`;
+ }
+ return "";
+ }
+
+ static hasPagination(data) {
+ return "pagination" in data;
+ }
+
+ hasPagination() {
+ if (!this.data) {
+ throw new Error(
+ `Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
+ );
+ }
+ return Pagination.hasPagination(this.data);
+ }
+
+ circularReferenceCheck(data) {
+ let key = data.pagination.data;
+ let includedTags = TemplateData.getIncludedTagNames(data);
+
+ for (let tag of includedTags) {
+ if (`collections.${tag}` === key) {
+ throw new PaginationError(
+ `Pagination circular reference${this.inputPathForErrorMessages}, data:\`${key}\` iterates over both the \`${tag}\` collection and also supplies pages to that collection.`,
+ );
+ }
+ }
+ }
+
+ setData(data) {
+ this.data = data || {};
+ this.target = [];
+
+ if (!this.hasPagination()) {
+ return;
+ }
+
+ if (!data.pagination) {
+ throw new Error(
+ `Misconfigured pagination data in template front matter${this.inputPathForErrorMessages} (YAML front matter precaution: did you use tabs and not spaces for indentation?).`,
+ );
+ } else if (!("size" in data.pagination)) {
+ throw new Error(
+ `Missing pagination size in front matter data${this.inputPathForErrorMessages}`,
+ );
+ }
+ this.circularReferenceCheck(data);
+
+ this.size = data.pagination.size;
+ this.alias = data.pagination.alias;
+ this.fullDataSet = this._get(this.data, this._getDataKey());
+ // this returns an array
+ this.target = this._resolveItems();
+ this.chunkedItems = this.pagedItems;
+ }
+
+ setTemplate(tmpl) {
+ this.template = tmpl;
+ }
+
+ _getDataKey() {
+ return this.data.pagination.data;
+ }
+
+ shouldResolveDataToObjectValues() {
+ if ("resolve" in this.data.pagination) {
+ return this.data.pagination.resolve === "values";
+ }
+ return false;
+ }
+
+ isFiltered(value) {
+ if ("filter" in this.data.pagination) {
+ let filtered = this.data.pagination.filter;
+ if (Array.isArray(filtered)) {
+ return filtered.indexOf(value) > -1;
+ }
+
+ return filtered === value;
+ }
+
+ return false;
+ }
+
+ _has(target, key) {
+ let notFoundValue = "__NOT_FOUND_ERROR__";
+ let data = lodashGet(target, key, notFoundValue);
+ return data !== notFoundValue;
+ }
+
+ _get(target, key) {
+ let notFoundValue = "__NOT_FOUND_ERROR__";
+ let data = lodashGet(target, key, notFoundValue);
+ if (data === notFoundValue) {
+ throw new Error(
+ `Could not find pagination data${this.inputPathForErrorMessages}, went looking for: ${key}`,
+ );
+ }
+ return data;
+ }
+
+ _resolveItems() {
+ let keys;
+ if (Array.isArray(this.fullDataSet)) {
+ keys = this.fullDataSet;
+ this.paginationTargetType = "array";
+ } else if (isPlainObject(this.fullDataSet)) {
+ this.paginationTargetType = "object";
+ if (this.shouldResolveDataToObjectValues()) {
+ keys = Object.values(this.fullDataSet);
+ } else {
+ keys = Object.keys(this.fullDataSet);
+ }
+ } else {
+ throw new Error(
+ `Unexpected data found in pagination target${this.inputPathForErrorMessages}: expected an Array or an Object.`,
+ );
+ }
+
+ // keys must be an array
+ let result = keys.slice();
+
+ if (this.data.pagination.before && typeof this.data.pagination.before === "function") {
+ // we don’t need to make a copy of this because we .slice() above to create a new copy
+ let fns = {};
+ if (this.config) {
+ fns = this.config.javascriptFunctions;
+ }
+ result = this.data.pagination.before.call(fns, result, this.data);
+ }
+
+ if (this.data.pagination.reverse === true) {
+ result = result.reverse();
+ }
+
+ if (this.data.pagination.filter) {
+ result = result.filter((value) => !this.isFiltered(value));
+ }
+
+ return result;
+ }
+
+ get pagedItems() {
+ if (!this.data) {
+ throw new Error(
+ `Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
+ );
+ }
+
+ const chunks = lodashChunk(this.target, this.size);
+ if (this.data.pagination?.generatePageOnEmptyData) {
+ return chunks.length ? chunks : [[]];
+ } else {
+ return chunks;
+ }
+ }
+
+ getPageCount() {
+ if (!this.hasPagination()) {
+ return 0;
+ }
+
+ return this.chunkedItems.length;
+ }
+
+ getNormalizedItems(pageItems) {
+ return this.size === 1 ? pageItems[0] : pageItems;
+ }
+
+ getOverrideDataPages(items, pageNumber) {
+ return {
+ // See Issue #345 for more examples
+ page: {
+ previous: pageNumber > 0 ? this.getNormalizedItems(items[pageNumber - 1]) : null,
+ next: pageNumber < items.length - 1 ? this.getNormalizedItems(items[pageNumber + 1]) : null,
+ first: items.length ? this.getNormalizedItems(items[0]) : null,
+ last: items.length ? this.getNormalizedItems(items[items.length - 1]) : null,
+ },
+
+ pageNumber,
+ };
+ }
+
+ getOverrideDataLinks(pageNumber, templateCount, links) {
+ let obj = {};
+
+ // links are okay but hrefs are better
+ obj.previousPageLink = pageNumber > 0 ? links[pageNumber - 1] : null;
+ obj.previous = obj.previousPageLink;
+
+ obj.nextPageLink = pageNumber < templateCount - 1 ? links[pageNumber + 1] : null;
+ obj.next = obj.nextPageLink;
+
+ obj.firstPageLink = links.length > 0 ? links[0] : null;
+ obj.lastPageLink = links.length > 0 ? links[links.length - 1] : null;
+
+ obj.links = links;
+ // todo deprecated, consistency with collections and use links instead
+ obj.pageLinks = links;
+ return obj;
+ }
+
+ getOverrideDataHrefs(pageNumber, templateCount, hrefs) {
+ let obj = {};
+
+ // hrefs are better than links
+ obj.previousPageHref = pageNumber > 0 ? hrefs[pageNumber - 1] : null;
+ obj.nextPageHref = pageNumber < templateCount - 1 ? hrefs[pageNumber + 1] : null;
+
+ obj.firstPageHref = hrefs.length > 0 ? hrefs[0] : null;
+ obj.lastPageHref = hrefs.length > 0 ? hrefs[hrefs.length - 1] : null;
+
+ obj.hrefs = hrefs;
+
+ // better names
+ obj.href = {
+ previous: obj.previousPageHref,
+ next: obj.nextPageHref,
+ first: obj.firstPageHref,
+ last: obj.lastPageHref,
+ };
+
+ return obj;
+ }
+
+ async getPageTemplates() {
+ if (!this.data) {
+ throw new Error(
+ `Missing \`setData\` call for Pagination object${this.inputPathForErrorMessages}`,
+ );
+ }
+
+ if (!this.hasPagination()) {
+ return [];
+ }
+
+ let entries = [];
+ let items = this.chunkedItems;
+ let pages = this.size === 1 ? items.map((entry) => entry[0]) : items;
+
+ let links = [];
+ let hrefs = [];
+
+ let hasPermalinkField =
+ Boolean(this.data[this.config.keys.permalink]) ||
+ Boolean(this.data.eleventyComputed?.[this.config.keys.permalink]);
+
+ // Do *not* pass collections through DeepCopy, we’ll re-add them back in later.
+ let collections = this.data.collections;
+ if (collections) {
+ delete this.data.collections;
+ }
+
+ let parentData = DeepCopy(
+ {
+ pagination: {
+ data: this.data.pagination.data,
+ size: this.data.pagination.size,
+ alias: this.alias,
+ pages,
+ },
+ },
+ this.data,
+ );
+
+ // Restore skipped collections
+ if (collections) {
+ this.data.collections = collections;
+ // Keep the original reference to the collections, no deep copy!!
+ parentData.collections = collections;
+ }
+
+ // TODO this does work fine but let’s wait on enabling it.
+ // DeepFreeze(parentData, ["collections"]);
+
+ // TODO future improvement dea: use a light Template wrapper for paged template clones (PagedTemplate?)
+ // so that we don’t have the memory cost of the full template (and can reuse the parent
+ // template for some things)
+
+ let indices = new Set();
+ for (let j = 0; j <= items.length - 1; j++) {
+ indices.add(j);
+ }
+
+ for (let pageNumber of indices) {
+ let cloned = await this.template.clone();
+
+ if (pageNumber > 0 && !hasPermalinkField) {
+ cloned.setExtraOutputSubdirectory(pageNumber);
+ }
+
+ let paginationData = {
+ pagination: {
+ items: items[pageNumber],
+ },
+ page: {},
+ };
+ Object.assign(paginationData.pagination, this.getOverrideDataPages(items, pageNumber));
+
+ if (this.alias) {
+ lodashSet(paginationData, this.alias, this.getNormalizedItems(items[pageNumber]));
+ }
+
+ // Do *not* deep merge pagination data! See https://github.com/11ty/eleventy/issues/147#issuecomment-440802454
+ let clonedData = ProxyWrap(paginationData, parentData);
+
+ // Previous method:
+ // let clonedData = DeepCopy(paginationData, parentData);
+
+ let { /*linkInstance,*/ rawPath, path, href } = await cloned.getOutputLocations(clonedData);
+ // TODO subdirectory to links if the site doesn’t live at /
+ if (rawPath) {
+ links.push("/" + rawPath);
+ }
+
+ hrefs.push(href);
+
+ // page.url and page.outputPath are used to avoid another getOutputLocations call later, see Template->addComputedData
+ clonedData.page.url = href;
+ clonedData.page.outputPath = path;
+
+ entries.push({
+ pageNumber,
+
+ // This is used by i18n Plugin to allow subgroups of nested pagination to be separate
+ groupNumber: items[pageNumber]?.[0]?.eleventyPaginationGroupNumber,
+
+ template: cloned,
+ data: clonedData,
+ });
+ }
+
+ // we loop twice to pass in the appropriate prev/next links (already full generated now)
+ let index = 0;
+ for (let pageEntry of entries) {
+ let linksObj = this.getOverrideDataLinks(index, items.length, links);
+
+ Object.assign(pageEntry.data.pagination, linksObj);
+
+ let hrefsObj = this.getOverrideDataHrefs(index, items.length, hrefs);
+ Object.assign(pageEntry.data.pagination, hrefsObj);
+ index++;
+ }
+
+ return entries;
+ }
+}
+
+export default Pagination;
diff --git a/node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js b/node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js
new file mode 100644
index 0000000..974b0e1
--- /dev/null
+++ b/node_modules/@11ty/eleventy/src/Plugins/RenderPlugin.js
@@ -0,0 +1,520 @@
+import fs from "node:fs";
+import { Merge, TemplatePath, isPlainObject } from "@11ty/eleventy-utils";
+import { evalToken } from "liquidjs";
+
+// TODO add a first-class Markdown component to expose this using Markdown-only syntax (will need to be synchronous for markdown-it)
+
+import { ProxyWrap } from "../Util/Objects/ProxyWrap.js";
+import TemplateDataInitialGlobalData from "../Data/TemplateDataInitialGlobalData.js";
+import EleventyBaseError from "../Errors/EleventyBaseError.js";
+import TemplateRender from "../TemplateRender.js";
+import ProjectDirectories from "../Util/ProjectDirectories.js";
+import TemplateConfig from "../TemplateConfig.js";
+import EleventyExtensionMap from "../EleventyExtensionMap.js";
+import TemplateEngineManager from "../Engines/TemplateEngineManager.js";
+import Liquid from "../Engines/Liquid.js";
+
+class EleventyNunjucksError extends EleventyBaseError {}
+
+/** @this {object} */
+async function compile(content, templateLang, options = {}) {
+ let { templateConfig, extensionMap } = options;
+ let strictMode = options.strictMode ?? false;
+
+ if (!templateConfig) {
+ templateConfig = new TemplateConfig(null, false);
+ templateConfig.setDirectories(new ProjectDirectories());
+ await templateConfig.init();
+ }
+
+ // Breaking change in 2.0+, previous default was `html` and now we default to the page template syntax
+ if (!templateLang) {
+ templateLang = this.page.templateSyntax;
+ }
+
+ if (!extensionMap) {
+ if (strictMode) {
+ throw new Error("Internal error: missing `extensionMap` in RenderPlugin->compile.");
+ }
+ extensionMap = new EleventyExtensionMap(templateConfig);
+ extensionMap.engineManager = new TemplateEngineManager(templateConfig);
+ }
+ let tr = new TemplateRender(templateLang, templateConfig);
+ tr.extensionMap = extensionMap;
+
+ if (templateLang) {
+ await tr.setEngineOverride(templateLang);
+ } else {
+ await tr.init();
+ }
+
+ // TODO tie this to the class, not the extension
+ if (
+ tr.engine.name === "11ty.js" ||
+ tr.engine.name === "11ty.cjs" ||
+ tr.engine.name === "11ty.mjs"
+ ) {
+ throw new Error(
+ "11ty.js is not yet supported as a template engine for `renderTemplate`. Use `renderFile` instead!",
+ );
+ }
+
+ return tr.getCompiledTemplate(content);
+}
+
+// No templateLang default, it should infer from the inputPath.
+async function compileFile(inputPath, options = {}, templateLang) {
+ let { templateConfig, extensionMap, config } = options;
+ let strictMode = options.strictMode ?? false;
+ if (!inputPath) {
+ throw new Error("Missing file path argument passed to the `renderFile` shortcode.");
+ }
+
+ let wasTemplateConfigMissing = false;
+ if (!templateConfig) {
+ templateConfig = new TemplateConfig(null, false);
+ templateConfig.setDirectories(new ProjectDirectories());
+ wasTemplateConfigMissing = true;
+ }
+ if (config && typeof config === "function") {
+ await config(templateConfig.userConfig);
+ }
+ if (wasTemplateConfigMissing) {
+ await templateConfig.init();
+ }
+
+ let normalizedPath = TemplatePath.normalizeOperatingSystemFilePath(inputPath);
+ // Prefer the exists cache, if it’s available
+ if (!templateConfig.existsCache.exists(normalizedPath)) {
+ throw new Error(
+ "Could not find render plugin file for the `renderFile` shortcode, looking for: " + inputPath,
+ );
+ }
+
+ if (!extensionMap) {
+ if (strictMode) {
+ throw new Error("Internal error: missing `extensionMap` in RenderPlugin->compileFile.");
+ }
+
+ extensionMap = new EleventyExtensionMap(templateConfig);
+ extensionMap.engineManager = new TemplateEngineManager(templateConfig);
+ }
+ let tr = new TemplateRender(inputPath, templateConfig);
+ tr.extensionMap = extensionMap;
+
+ if (templateLang) {
+ await tr.setEngineOverride(templateLang);
+ } else {
+ await tr.init();
+ }
+
+ if (!tr.engine.needsToReadFileContents()) {
+ return tr.getCompiledTemplate(null);
+ }
+
+ // TODO we could make this work with full templates (with front matter?)
+ let content = fs.readFileSync(inputPath, "utf8");
+ return tr.getCompiledTemplate(content);
+}
+
+/** @this {object} */
+async function renderShortcodeFn(fn, data) {
+ if (fn === undefined) {
+ return;
+ } else if (typeof fn !== "function") {
+ throw new Error(`The \`compile\` function did not return a function. Received ${fn}`);
+ }
+
+ // if the user passes a string or other literal, remap to an object.
+ if (!isPlainObject(data)) {
+ data = {
+ _: data,
+ };
+ }
+
+ if ("data" in this && isPlainObject(this.data)) {
+ // when options.accessGlobalData is true, this allows the global data
+ // to be accessed inside of the shortcode as a fallback
+
+ data = ProxyWrap(data, this.data);
+ } else {
+ // save `page` and `eleventy` for reuse
+ data.page = this.page;
+ data.eleventy = this.eleventy;
+ }
+
+ return fn(data);
+}
+
+/**
+ * @module 11ty/eleventy/Plugins/RenderPlugin
+ */
+
+/**
+ * A plugin to add shortcodes to render an Eleventy template
+ * string (or file) inside of another template. {@link https://v3.11ty.dev/docs/plugins/render/}
+ *
+ * @since 1.0.0
+ * @param {module:11ty/eleventy/UserConfig} eleventyConfig - User-land configuration instance.
+ * @param {object} options - Plugin options
+ */
+function eleventyRenderPlugin(eleventyConfig, options = {}) {
+ let templateConfig;
+ eleventyConfig.on("eleventy.config", (tmplConfigInstance) => {
+ templateConfig = tmplConfigInstance;
+ });
+
+ let extensionMap;
+ eleventyConfig.on("eleventy.extensionmap", (map) => {
+ extensionMap = map;
+ });
+
+ /**
+ * @typedef {object} options
+ * @property {string} [tagName] - The shortcode name to render a template string.
+ * @property {string} [tagNameFile] - The shortcode name to render a template file.
+ * @property {module:11ty/eleventy/TemplateConfig} [templateConfig] - Configuration object
+ * @property {boolean} [accessGlobalData] - Whether or not the template has access to the page’s data.
+ */
+ let defaultOptions = {
+ tagName: "renderTemplate",
+ tagNameFile: "renderFile",
+ filterName: "renderContent",
+ templateConfig: null,
+ accessGlobalData: false,
+ };
+ let opts = Object.assign(defaultOptions, options);
+
+ function liquidTemplateTag(liquidEngine, tagName) {
+ // via https://github.com/harttle/liquidjs/blob/b5a22fa0910c708fe7881ef170ed44d3594e18f3/src/builtin/tags/raw.ts
+ return {
+ parse: function (tagToken, remainTokens) {
+ this.name = tagToken.name;
+
+ if (eleventyConfig.liquid.parameterParsing === "builtin") {
+ this.orderedArgs = Liquid.parseArgumentsBuiltin(tagToken.args);
+ // note that Liquid does have a Hash class for name-based argument parsing but offers no easy to support both modes in one class
+ } else {
+ this.legacyArgs = tagToken.args;
+ }
+
+ this.tokens = [];
+
+ var stream = liquidEngine.parser
+ .parseStream(remainTokens)
+ .on("token", (token) => {
+ if (token.name === "end" + tagName) stream.stop();
+ else this.tokens.push(token);
+ })
+ .on("end", () => {
+ throw new Error(`tag ${tagToken.getText()} not closed`);
+ });
+
+ stream.start();
+ },
+ render: function* (ctx) {
+ let normalizedContext = {};
+ if (ctx) {
+ if (opts.accessGlobalData) {
+ // parent template data cascade
+ normalizedContext.data = ctx.getAll();
+ }
+
+ normalizedContext.page = ctx.get(["page"]);
+ normalizedContext.eleventy = ctx.get(["eleventy"]);
+ }
+
+ let argArray = [];
+ if (this.legacyArgs) {
+ let rawArgs = Liquid.parseArguments(null, this.legacyArgs);
+ for (let arg of rawArgs) {
+ let b = yield liquidEngine.evalValue(arg, ctx);
+ argArray.push(b);
+ }
+ } else if (this.orderedArgs) {
+ for (let arg of this.orderedArgs) {
+ let b = yield evalToken(arg, ctx);
+ argArray.push(b);
+ }
+ }
+
+ // plaintext paired shortcode content
+ let body = this.tokens.map((token) => token.getText()).join("");
+
+ let ret = _renderStringShortcodeFn.call(
+ normalizedContext,
+ body,
+ // templateLang, data
+ ...argArray,
+ );
+ yield ret;
+ return ret;
+ },
+ };
+ }
+
+ // TODO I don’t think this works with whitespace control, e.g. {%- endrenderTemplate %}
+ function nunjucksTemplateTag(NunjucksLib, tagName) {
+ return new (function () {
+ this.tags = [tagName];
+
+ this.parse = function (parser, nodes) {
+ var tok = parser.nextToken();
+
+ var args = parser.parseSignature(true, true);
+ const begun = parser.advanceAfterBlockEnd(tok.value);
+
+ // This code was ripped from the Nunjucks parser for `raw`
+ // https://github.com/mozilla/nunjucks/blob/fd500902d7c88672470c87170796de52fc0f791a/nunjucks/src/parser.js#L655
+ const endTagName = "end" + tagName;
+ // Look for upcoming raw blocks (ignore all other kinds of blocks)
+ const rawBlockRegex = new RegExp(
+ "([\\s\\S]*?){%\\s*(" + tagName + "|" + endTagName + ")\\s*(?=%})%}",
+ );
+ let rawLevel = 1;
+ let str = "";
+ let matches = null;
+
+ // Exit when there's nothing to match
+ // or when we've found the matching "endraw" block
+ while ((matches = parser.tokens._extractRegex(rawBlockRegex)) && rawLevel > 0) {
+ const all = matches[0];
+ const pre = matches[1];
+ const blockName = matches[2];
+
+ // Adjust rawlevel
+ if (blockName === tagName) {
+ rawLevel += 1;
+ } else if (blockName === endTagName) {
+ rawLevel -= 1;
+ }
+
+ // Add to str
+ if (rawLevel === 0) {
+ // We want to exclude the last "endraw"
+ str += pre;
+ // Move tokenizer to beginning of endraw block
+ parser.tokens.backN(all.length - pre.length);
+ } else {
+ str += all;
+ }
+ }
+
+ let body = new nodes.Output(begun.lineno, begun.colno, [
+ new nodes.TemplateData(begun.lineno, begun.colno, str),
+ ]);
+ return new nodes.CallExtensionAsync(this, "run", args, [body]);
+ };
+
+ this.run = function (...args) {
+ let resolve = args.pop();
+ let body = args.pop();
+ let [context, ...argArray] = args;
+
+ let normalizedContext = {};
+ if (context.ctx?.page) {
+ normalizedContext.ctx = context.ctx;
+
+ // TODO .data
+ // if(opts.accessGlobalData) {
+ // normalizedContext.data = context.ctx;
+ // }
+
+ normalizedContext.page = context.ctx.page;
+ normalizedContext.eleventy = context.ctx.eleventy;
+ }
+
+ body(function (e, bodyContent) {
+ if (e) {
+ resolve(
+ new EleventyNunjucksError(`Error with Nunjucks paired shortcode \`${tagName}\``, e),
+ );
+ }
+
+ Promise.resolve(
+ _renderStringShortcodeFn.call(
+ normalizedContext,
+ bodyContent,
+ // templateLang, data
+ ...argArray,
+ ),
+ ).then(
+ function (returnValue) {
+ resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
+ },
+ function (e) {
+ resolve(
+ new EleventyNunjucksError(`Error with Nunjucks paired shortcode \`${tagName}\``, e),
+ null,
+ );
+ },
+ );
+ });
+ };
+ })();
+ }
+
+ /** @this {object} */
+ async function _renderStringShortcodeFn(content, templateLang, data = {}) {
+ // Default is fn(content, templateLang, data) but we want to support fn(content, data) too
+ if (typeof templateLang !== "string") {
+ data = templateLang;
+ templateLang = false;
+ }
+
+ // TODO Render plugin `templateLang` is feeding bad input paths to the addDependencies call in Custom.js
+ let fn = await compile.call(this, content, templateLang, {
+ templateConfig: opts.templateConfig || templateConfig,
+ extensionMap,
+ });
+
+ return renderShortcodeFn.call(this, fn, data);
+ }
+
+ /** @this {object} */
+ async function _renderFileShortcodeFn(inputPath, data = {}, templateLang) {
+ let options = {
+ templateConfig: opts.templateConfig || templateConfig,
+ extensionMap,
+ };
+
+ let fn = await compileFile.call(this, inputPath, options, templateLang);
+
+ return renderShortcodeFn.call(this, fn, data);
+ }
+
+ // Render strings
+ if (opts.tagName) {
+ // use falsy to opt-out
+ eleventyConfig.addJavaScriptFunction(opts.tagName, _renderStringShortcodeFn);
+
+ eleventyConfig.addLiquidTag(opts.tagName, function (liquidEngine) {
+ return liquidTemplateTag(liquidEngine, opts.tagName);
+ });
+
+ eleventyConfig.addNunjucksTag(opts.tagName, function (nunjucksLib) {
+ return nunjucksTemplateTag(nunjucksLib, opts.tagName);
+ });
+ }
+
+ // Filter for rendering strings
+ if (opts.filterName) {
+ eleventyConfig.addAsyncFilter(opts.filterName, _renderStringShortcodeFn);
+ }
+
+ // Render File
+ // use `false` to opt-out
+ if (opts.tagNameFile) {
+ eleventyConfig.addAsyncShortcode(opts.tagNameFile, _renderFileShortcodeFn);
+ }
+}
+
+// Will re-use the same configuration instance both at a top level and across any nested renders
+class RenderManager {
+ /** @type {Promise|undefined} */
+ #hasConfigInitialized;
+ #extensionMap;
+ #templateConfig;
+
+ constructor() {
+ this.templateConfig = new TemplateConfig(null, false);
+ this.templateConfig.setDirectories(new ProjectDirectories());
+ }
+
+ get templateConfig() {
+ return this.#templateConfig;
+ }
+
+ set templateConfig(templateConfig) {
+ if (!templateConfig || templateConfig === this.#templateConfig) {
+ return;
+ }
+
+ this.#templateConfig = templateConfig;
+
+ // This is the only plugin running on the Edge
+ this.#templateConfig.userConfig.addPlugin(eleventyRenderPlugin, {
+ templateConfig: this.#templateConfig,
+ accessGlobalData: true,
+ });
+
+ this.#extensionMap = new EleventyExtensionMap(this.#templateConfig);
+ this.#extensionMap.engineManager = new TemplateEngineManager(this.#templateConfig);
+ }
+
+ async init() {
+ if (this.#hasConfigInitialized) {
+ return this.#hasConfigInitialized;
+ }
+ if (this.templateConfig.hasInitialized()) {
+ return true;
+ }
+ this.#hasConfigInitialized = this.templateConfig.init();
+ await this.#hasConfigInitialized;
+
+ return true;
+ }
+
+ // `callback` is async-friendly but requires await upstream
+ config(callback) {
+ // run an extra `function(eleventyConfig)` configuration callbacks
+ if (callback && typeof callback === "function") {
+ return callback(this.templateConfig.userConfig);
+ }
+ }
+
+ get initialGlobalData() {
+ if (!this._data) {
+ this._data = new TemplateDataInitialGlobalData(this.templateConfig);
+ }
+ return this._data;
+ }
+
+ // because we don’t have access to the full data cascade—but
+ // we still want configuration data added via `addGlobalData`
+ async getData(...data) {
+ await this.init();
+
+ let globalData = await this.initialGlobalData.getData();
+ let merged = Merge({}, globalData, ...data);
+ return merged;
+ }
+
+ async compile(content, templateLang, options = {}) {
+ await this.init();
+
+ options.templateConfig = this.templateConfig;
+ options.extensionMap = this.#extensionMap;
+ options.strictMode = true;
+
+ // We don’t need `compile.call(this)` here because the Edge always uses "liquid" as the template lang (instead of relying on this.page.templateSyntax)
+ // returns promise
+ return compile(content, templateLang, options);
+ }
+
+ async render(fn, edgeData, buildTimeData) {
+ await this.init();
+
+ let mergedData = await this.getData(edgeData);
+ // Set .data for options.accessGlobalData feature
+ let context = {
+ data: mergedData,
+ };
+
+ return renderShortcodeFn.call(context, fn, buildTimeData);
+ }
+}
+
+Object.defineProperty(eleventyRenderPlugin, "eleventyPackage", {
+ value: "@11ty/eleventy/render-plugin",
+});
+
+Object.defineProperty(eleventyRenderPlugin, "eleventyPluginOptions", {
+ value: {
+ unique: true,
+ },
+});
+
+export default eleventyRenderPlugin;
+
+export { compileFile as File, compile as String, RenderManager };