diff --git a/docs/content/docs/components/results.md b/docs/content/docs/components/results.md index c82fafc..0a84bdd 100644 --- a/docs/content/docs/components/results.md +++ b/docs/content/docs/components/results.md @@ -88,10 +88,11 @@ The template engine provides [built-in filters](https://github.com/bglw/adequate #### `resolveUrl(pageUrl)` -Resolves a relative URL against a page URL. Absolute URLs are returned unchanged. +Resolves a relative URL against a page URL. Absolute URLs are returned unchanged. If the page URL includes a domain, for example from a `baseUrl` on a merged index, the resolved URL keeps that domain. ``` "images/hero.png" | resolveUrl("/blog/post/") → /blog/post/images/hero.png +"images/hero.png" | resolveUrl("https://example.com/blog/post/") → https://example.com/blog/post/images/hero.png ``` ### Placeholder Template diff --git a/pagefind/integration_tests/ui/ui_base/pagefind-ui-resolves-image-urls-against-base-url.toolproof.yml b/pagefind/integration_tests/ui/ui_base/pagefind-ui-resolves-image-urls-against-base-url.toolproof.yml new file mode 100644 index 0000000..2387819 --- /dev/null +++ b/pagefind/integration_tests/ui/ui_base/pagefind-ui-resolves-image-urls-against-base-url.toolproof.yml @@ -0,0 +1,67 @@ +name: Base UI Tests > Pagefind UI resolves image URLs against an absolute base URL +steps: + - ref: ./background.toolproof.yml + - step: I have a "public/index.html" file with the content {html} + html: |- +
+ + + + - step: I have a "public/subs/index.html" file with the content {html} + html: |- + + + + + - step: I have a "public/blog/post/index.html" file with the content {html} + html: >- +
A page with a relative image + URL.
+ - step: I have a "public/about/index.html" file with the content {html} + html: >- +
A page with an absolute image + URL.
+ - macro: I run Pagefind + - step: stdout should contain "Running Pagefind" + - step: The file "public/pagefind/pagefind.js" should not be empty + - step: I serve the directory "public" + - step: In my browser, I load "/" + - step: In my browser, I evaluate {js} + js: window.pui.triggerSearch("image"); + - step: In my browser, the console should be empty + - snapshot: In my browser, the result of {js} + js: |- + await toolproof.waitFor(() => document.querySelectorAll(".pagefind-ui__result-image").length === 2); + let items = Array.from(document.querySelectorAll(".pagefind-ui__result")); + items.sort((a, b) => a.textContent.localeCompare(b.textContent)); + return items.map(li => { + let title = li.querySelector(".pagefind-ui__result-link").textContent; + return title + ": " + li.querySelector(".pagefind-ui__result-image").getAttribute("src"); + }).join("\n"); + snapshot_content: |- + ╎Absolute Image: https://example.com/images/hero.png + ╎Relative Image: https://example.com/blog/post/images/header.png + - step: In my browser, I load "/subs/" + - step: In my browser, I evaluate {js} + js: window.pui.triggerSearch("image"); + - step: In my browser, the console should be empty + - snapshot: In my browser, the result of {js} + js: |- + await toolproof.waitFor(() => document.querySelectorAll(".pagefind-ui__result-image").length === 2); + let items = Array.from(document.querySelectorAll(".pagefind-ui__result")); + items.sort((a, b) => a.textContent.localeCompare(b.textContent)); + return items.map(li => { + let title = li.querySelector(".pagefind-ui__result-link").textContent; + return title + ": " + li.querySelector(".pagefind-ui__result-image").getAttribute("src"); + }).join("\n"); + snapshot_content: |- + ╎Absolute Image: https://example.com/images/hero.png + ╎Relative Image: https://example.com/blog/post/images/header.png diff --git a/pagefind/integration_tests/web_components/relative-image-urls-base-url.toolproof.yml b/pagefind/integration_tests/web_components/relative-image-urls-base-url.toolproof.yml new file mode 100644 index 0000000..02350cd --- /dev/null +++ b/pagefind/integration_tests/web_components/relative-image-urls-base-url.toolproof.yml @@ -0,0 +1,49 @@ +name: Web Components Tests > Image URLs are resolved against an absolute base URL +steps: + - step: I have the environment variable "PAGEFIND_SITE" set to "public" + - step: I have a "public/index.html" file with the content {html} + html: |- + + + +
A page with a relative image + URL.
+ - step: I have a "public/about/index.html" file with the content {html} + html: >- +
A page with an absolute image + URL.
+ - macro: I run Pagefind + - step: stdout should contain "Running Pagefind" + - step: I serve the directory "public" + - step: In my browser, I load "/" + - step: In my browser, I click the selector "pagefind-input input" + - step: In my browser, I type "image" + - step: In my browser, the console should be empty + - step: In my browser, I evaluate {js} + js: |- + let results = await toolproof.querySelector("pagefind-results ul"); + await toolproof.waitFor(() => results.querySelectorAll("li a").length === 2); + - snapshot: In my browser, the result of {js} + js: |- + let results = await toolproof.querySelector("pagefind-results ul"); + let items = Array.from(results.querySelectorAll("li")); + items.sort((a, b) => a.textContent.localeCompare(b.textContent)); + return items.map(li => { + let title = li.querySelector("a").textContent; + let img = li.querySelector("img"); + return title + ": " + (img ? img.getAttribute("src") : "no image"); + }).join("\n"); + snapshot_content: |- + ╎Absolute Image: https://example.com/images/hero.png + ╎Relative Image: https://example.com/blog/post/images/header.png diff --git a/pagefind_ui/component/components/index.ts b/pagefind_ui/component/components/index.ts index c9ce0e5..6f38920 100644 --- a/pagefind_ui/component/components/index.ts +++ b/pagefind_ui/component/components/index.ts @@ -4,9 +4,12 @@ import { registerFunction } from "adequate-little-templates"; registerFunction("resolveUrl", (url, pageUrl) => { const s = String(url ?? ""); - if (!s || /^[a-z][a-z0-9+.-]*:/i.test(s) || /^\/\//.test(s) || s.startsWith("/")) return s; + if (!s || /^[a-z][a-z0-9+.-]*:/i.test(s) || /^\/\//.test(s)) return s; try { - return new URL(s, new URL(String(pageUrl ?? "/"), "https://p")).pathname; + const resolved = new URL(s, new URL(String(pageUrl ?? "/"), "https://p")); + return resolved.origin === "https://p" + ? resolved.pathname + resolved.search + resolved.hash + : resolved.href; } catch { return s; } diff --git a/pagefind_ui/default/svelte/result.svelte b/pagefind_ui/default/svelte/result.svelte index eb44288..e8978b5 100644 --- a/pagefind_ui/default/svelte/result.svelte +++ b/pagefind_ui/default/svelte/result.svelte @@ -9,9 +9,10 @@ let meta = []; const resolveImageUrl = (src, pageUrl) => { - if (!src || /^[a-z][a-z0-9+.-]*:/i.test(src) || /^\/\//.test(src) || src.startsWith("/")) return src; + if (!src || /^[a-z][a-z0-9+.-]*:/i.test(src) || /^\/\//.test(src)) return src; try { - return new URL(src, new URL(pageUrl || "/", "https://p")).pathname; + const url = new URL(src, new URL(pageUrl || "/", "https://p")); + return url.origin === "https://p" ? url.pathname + url.search + url.hash : url.href; } catch { return src; } diff --git a/pagefind_ui/default/svelte/result_with_subs.svelte b/pagefind_ui/default/svelte/result_with_subs.svelte index 5e0cd4d..1392161 100644 --- a/pagefind_ui/default/svelte/result_with_subs.svelte +++ b/pagefind_ui/default/svelte/result_with_subs.svelte @@ -10,6 +10,16 @@ let non_root_sub_results = []; let has_root_sub_result = false; + const resolveImageUrl = (src, pageUrl) => { + if (!src || /^[a-z][a-z0-9+.-]*:/i.test(src) || /^\/\//.test(src)) return src; + try { + const url = new URL(src, new URL(pageUrl || "/", "https://p")); + return url.origin === "https://p" ? url.pathname + url.search + url.hash : url.href; + } catch { + return src; + } + }; + const thin_sub_results = (results, limit) => { if (results.length <= limit) { return results; @@ -26,6 +36,9 @@ const load = async (r) => { data = await r.data(); data = process_result?.(data) ?? data; + if (data.meta?.image) { + data = { ...data, meta: { ...data.meta, image: resolveImageUrl(data.meta.image, data.meta.url || data.url) } }; + } meta = Object.entries(data.meta).filter(([key]) => !skipMeta.includes(key)); if (Array.isArray(data.sub_results)) { has_root_sub_result =