diff --git a/src/Plugins/InputPathToUrl.js b/src/Plugins/InputPathToUrl.js
index da033ff0b..f2683c9fa 100644
--- a/src/Plugins/InputPathToUrl.js
+++ b/src/Plugins/InputPathToUrl.js
@@ -1,27 +1,47 @@
+import path from "node:path";
import { TemplatePath } from "@11ty/eleventy-utils";
import isValidUrl from "../Util/ValidUrl.js";
-import memoize from "../Util/MemoizeFunction.js";
-function normalizeInputPath(inputPath, inputDir, contentMap) {
+function getValidPath(contentMap, testPath) {
+ let normalized = TemplatePath.addLeadingDotSlash(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
- let normalized;
// Input directory already on the input path
- if (TemplatePath.join(inputPath).startsWith(TemplatePath.join(inputDir))) {
- normalized = inputPath;
- } else {
- normalized = TemplatePath.join(inputDir, inputPath);
+ if (TemplatePath.join(targetInputPath).startsWith(TemplatePath.join(inputDir))) {
+ let absolutePath = getValidPath(contentMap, targetInputPath);
+ if (absolutePath) {
+ return absolutePath;
+ }
}
- normalized = TemplatePath.addLeadingDotSlash(normalized);
+ // Relative to project input directory
+ let relativeToInputDir = getValidPath(contentMap, TemplatePath.join(inputDir, targetInputPath));
+ if (relativeToInputDir) {
+ return relativeToInputDir;
+ }
- // it must exist in the content map to be valid
- if (contentMap[normalized]) {
- return normalized;
+ 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 inputPath;
+ return targetInputPath;
}
function parseFilePath(filepath) {
@@ -63,30 +83,30 @@ function FilterPlugin(eleventyConfig) {
contentMap = inputPathToUrl;
});
- eleventyConfig.addFilter(
- "inputPathToUrl",
- memoize(function (filepath) {
- if (!contentMap) {
- throw new Error("Internal error: contentMap not available for `inputPathToUrl` filter.");
- }
-
- if (isValidUrl(filepath)) {
- return filepath;
- }
-
- let inputDir = eleventyConfig.directories.input;
- let suffix = "";
- [suffix, filepath] = parseFilePath(filepath);
- filepath = normalizeInputPath(filepath, inputDir, contentMap);
-
- let urls = contentMap[filepath];
- if (!urls || urls.length === 0) {
- throw new Error("`inputPathToUrl` filter could not find a matching target for " + filepath);
- }
-
- return `${urls[0]}${suffix}`;
- }),
- );
+ 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);
+ // @ts-ignore
+ targetFilePath = normalizeInputPath(targetFilePath, inputDir, 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 = {}) {
@@ -102,24 +122,30 @@ function TransformPlugin(eleventyConfig, defaultOptions = {}) {
contentMap = inputPathToUrl;
});
- eleventyConfig.htmlTransformer.addUrlTransform(opts.extensions, function (filepathOrUrl) {
+ eleventyConfig.htmlTransformer.addUrlTransform(opts.extensions, function (targetFilepathOrUrl) {
if (!contentMap) {
throw new Error("Internal error: contentMap not available for the `pathToUrl` Transform.");
}
- if (isValidUrl(filepathOrUrl)) {
- return filepathOrUrl;
+ if (isValidUrl(targetFilepathOrUrl)) {
+ return targetFilepathOrUrl;
}
let inputDir = eleventyConfig.directories.input;
let suffix = "";
- [suffix, filepathOrUrl] = parseFilePath(filepathOrUrl);
- filepathOrUrl = normalizeInputPath(filepathOrUrl, inputDir, contentMap);
-
- let urls = contentMap[filepathOrUrl];
- if (!filepathOrUrl || !urls || urls.length === 0) {
+ [suffix, targetFilepathOrUrl] = parseFilePath(targetFilepathOrUrl);
+ // @ts-ignore
+ targetFilepathOrUrl = normalizeInputPath(
+ targetFilepathOrUrl,
+ inputDir,
+ 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 `${filepathOrUrl}${suffix}`;
+ return `${targetFilepathOrUrl}${suffix}`;
}
return `${urls[0]}${suffix}`;
diff --git a/test/InputPathToUrlPluginTest.js b/test/InputPathToUrlPluginTest.js
index b1058d40c..0dc665142 100644
--- a/test/InputPathToUrlPluginTest.js
+++ b/test/InputPathToUrlPluginTest.js
@@ -61,9 +61,6 @@ test("Using the transform (and the filter too)", async (t) => {
},
});
- elev.setIsVerbose(false);
- elev.disableLogger();
-
let results = await elev.toJSON();
// filter is already available in the default config.
t.is(
@@ -82,9 +79,6 @@ test("Using the filter", async (t) => {
configPath: false,
});
- elev.setIsVerbose(false);
- elev.disableLogger();
-
let results = await elev.toJSON();
t.is(
getContentFor(results, "/filter/index.html"),
@@ -106,9 +100,6 @@ test("Using the transform and the base plugin", async (t) => {
},
});
- elev.setIsVerbose(false);
- elev.disableLogger();
-
let results = await elev.toJSON();
t.is(
getContentFor(results, "/filter/index.html"),
@@ -130,9 +121,6 @@ test("Using the transform and the base plugin, reverse order", async (t) => {
},
});
- elev.setIsVerbose(false);
- elev.disableLogger();
-
let results = await elev.toJSON();
t.is(
getContentFor(results, "/filter/index.html"),
@@ -144,3 +132,43 @@ test("Using the transform and the base plugin, reverse order", async (t) => {
);
});
+
+test("Issue #3417 Using the transform with relative path (dot slash)", async (t) => {
+ let elev = new Eleventy("./test/stubs-virtual/", "./test/stubs-virtual/_site", {
+ configPath: false,
+ config: function (eleventyConfig) {
+ // FilterPlugin is available in the default config.
+ eleventyConfig.addPlugin(TransformPlugin);
+
+ eleventyConfig.addTemplate("source/test.njk", `Target`)
+ eleventyConfig.addTemplate("source/target.njk", "lol")
+ },
+ });
+
+ let results = await elev.toJSON();
+ // filter is already available in the default config.
+ t.is(
+ getContentFor(results, "/source/test/index.html"),
+ `Target`
+ );
+});
+
+test("Issue #3417 Using the transform with relative path (no dot slash)", async (t) => {
+ let elev = new Eleventy("./test/stubs-virtual/", "./test/stubs-virtual/_site", {
+ configPath: false,
+ config: function (eleventyConfig) {
+ // FilterPlugin is available in the default config.
+ eleventyConfig.addPlugin(TransformPlugin);
+
+ eleventyConfig.addTemplate("source/test.njk", `Target`)
+ eleventyConfig.addTemplate("source/target.njk", "lol")
+ },
+ });
+
+ let results = await elev.toJSON();
+ // filter is already available in the default config.
+ t.is(
+ getContentFor(results, "/source/test/index.html"),
+ `Target`
+ );
+});