diff --git a/packages/imagetools_3/api/utils/getFilteredProps.js b/packages/imagetools_3/api/utils/getFilteredProps.js
index 9c791ba..8b35a19 100644
--- a/packages/imagetools_3/api/utils/getFilteredProps.js
+++ b/packages/imagetools_3/api/utils/getFilteredProps.js
@@ -36,8 +36,8 @@ const NonProperties = {
};
const ImgProperties = NonGlobalSupportedConfigs.filter(
- (key) => !NonProperties.Img.includes(key)
- ),
+ (key) => !NonProperties.Img.includes(key)
+),
PictureProperties = NonGlobalSupportedConfigs.filter(
(key) => !NonProperties.Picture.includes(key)
),
@@ -65,14 +65,33 @@ export default function getFilteredProps(type, props) {
const { search, searchParams } = new URL(props.src, "file://");
+ const paramOptions = Object.fromEntries(searchParams);
+
+ // Separate supported config params from others (like cache busters)
+ const supportedKeys = SupportedProperties[type] || [];
+ const configParams = {};
+ const otherParams = new URLSearchParams();
+
+ for (const [key, value] of searchParams) {
+ if (supportedKeys.includes(key) || GlobalConfigOptions[key]) {
+ configParams[key] = value;
+ } else {
+ otherParams.append(key, value);
+ }
+ }
+
+ // Remove ALL params from src initially
props.src = props.src.replace(search, "");
- const paramOptions = Object.fromEntries(searchParams);
+ // Re-append ONLY the non-config params to src
+ if (otherParams.toString()) {
+ props.src += `?${otherParams.toString()}`;
+ }
const filteredLocalProps = filterConfigs(
type,
{
- ...paramOptions,
+ ...configParams,
...props,
},
SupportedProperties[type]
diff --git a/packages/imagetools_3/api/utils/getSrcset.js b/packages/imagetools_3/api/utils/getSrcset.js
index a9337ed..8aad65c 100644
--- a/packages/imagetools_3/api/utils/getSrcset.js
+++ b/packages/imagetools_3/api/utils/getSrcset.js
@@ -18,24 +18,39 @@ export default async function getSrcset(
const params = keys.length
? keys
- .map((key) =>
- Array.isArray(options[key])
- ? `&${key}=${options[key].join(";")}`
- : `&${key}=${options[key]}`
- )
- .join("")
+ .map((key) =>
+ Array.isArray(options[key])
+ ? `&${key}=${options[key].join(";")}`
+ : `&${key}=${options[key]}`
+ )
+ .join("")
: "";
- const [cleanSrc] = src.split("?");
- const id = `${cleanSrc}?${params.slice(1)}`;
+ // Extract existing search params from src
+ const [cleanSrc, search] = src.split("?");
+
+ // Combine existing params (like s=...) with options
+ // Existing params come first so they can be overridden by options if needed,
+ // though 's' should typically be unique.
+ const searchParams = new URLSearchParams(search);
+
+ // Add options to searchParams
+ keys.forEach(key => {
+ const value = Array.isArray(options[key])
+ ? options[key].join(";")
+ : options[key];
+ searchParams.set(key, value);
+ });
+
+ const id = `${cleanSrc}?${searchParams.toString()}`;
// @todo : remove this
const fullPath = await getSrcPath(id);
const { default: load } = await import("../../plugin/hooks/load.js");
// @ts-ignore
let srcset = null
-
+
try {
-
+
const loaded = await load(fullPath, base);
if (loaded) {
srcset = loaded.slice(16, -1);
diff --git a/packages/imagetools_3/components/Picture.astro b/packages/imagetools_3/components/Picture.astro
index 737bd77..7b3c6d9 100644
--- a/packages/imagetools_3/components/Picture.astro
+++ b/packages/imagetools_3/components/Picture.astro
@@ -2,9 +2,18 @@
import renderPicture from "../api/renderPicture.js";
import type { PictureConfigOptions } from "../types.d";
-declare interface Props extends PictureConfigOptions {}
+declare interface Props extends PictureConfigOptions {
+ s?: string;
+}
-const { link, style, picture } = await renderPicture(Astro.props as Props);
+const { s, ...rest } = Astro.props as Props;
+
+if (s) {
+ const separator = rest.src.includes("?") ? "&" : "?";
+ rest.src = `${rest.src}${separator}s=${s}`;
+}
+
+const { link, style, picture } = await renderPicture(rest);
---
diff --git a/packages/imagetools_3/integration/index.js b/packages/imagetools_3/integration/index.js
index a20b4f2..d530735 100644
--- a/packages/imagetools_3/integration/index.js
+++ b/packages/imagetools_3/integration/index.js
@@ -88,7 +88,6 @@ export default {
await pMap(
[...allAssets.entries()],
async ([assetPath, { hash, image, buffer }]) => {
- logger.info(`[imagetools] Processing image ${assetPath}...`);
try {
await saveAndCopyAsset(
hash,
diff --git a/packages/imagetools_3/plugin/hooks/load.js b/packages/imagetools_3/plugin/hooks/load.js
index 9bda1d4..73ac4b5 100644
--- a/packages/imagetools_3/plugin/hooks/load.js
+++ b/packages/imagetools_3/plugin/hooks/load.js
@@ -26,9 +26,9 @@ export default async function load(id) {
const ext = path.extname(id).slice(1);
-
+
if (!supportedImageTypes.includes(ext)) return null;
-
+
const { default: astroViteConfigs } = await import(
// @ts-ignore
"../../astroViteConfigs.js"
@@ -42,9 +42,10 @@ export default async function load(id) {
path.relative("", src).split(path.sep).join(path.posix.sep)
);
+ // Include search params in the hash calculation to force re-processing on change
const getHash = (width) =>
objectHash(
- { width, options, rootRelativePosixSrc },
+ { width, options, rootRelativePosixSrc, search },
// @ts-ignore
{ algorithm: "sha256" }
);
@@ -56,8 +57,11 @@ export default async function load(id) {
const config = Object.fromEntries(searchParams);
+ // Use the full ID (including search params) + src for the store key
+ // This ensures that the same file with different params is treated as different source
+ const storeKey = src + search;
const { image: loadedImage, width: imageWidth } =
- store.get(src) || store.set(src, await getLoadedImage(src, ext)).get(src);
+ store.get(storeKey) || store.set(storeKey, await getLoadedImage(src, ext)).get(storeKey);
const { type, widths, options, extension, raw, inline } = getConfigOptions(
config,
@@ -97,16 +101,16 @@ export default async function load(id) {
const { image, buffer } = raw
? {
- image: sharp ? loadedImage : null,
- buffer: !sharp ? loadedImage.data : null,
- }
+ image: sharp ? loadedImage : null,
+ buffer: !sharp ? loadedImage.data : null,
+ }
: await getTransformedImage({
- src,
- image: loadedImage,
- config,
- type,
- })
-
+ src,
+ image: loadedImage,
+ config,
+ type,
+ })
+
const dataUri = `data:${type};base64,${(
buffer || (await getCachedBuffer(hash, image))
).toString("base64")}`
@@ -126,18 +130,18 @@ export default async function load(id) {
);
if (!store.has(assetPath)) {
- const config = { width, ...options };
+ const config = { width, ...options };
// Create cache key for this specific image transformation
- const cacheKey = {
- src: id,
- width,
- type,
+ const cacheKey = {
+ src: id,
+ width,
+ type,
extension,
- options: objectHash(options)
+ options: objectHash(options)
};
-
+
let imageObject = null;
-
+
// Only use cache in production builds
if (environment === "production") {
imageObject = await get_cached_object(cacheKey, 'imagetools-plugin');
@@ -150,15 +154,15 @@ export default async function load(id) {
if (!imageObject) {
const { image, buffer } = raw
? {
- image: sharp && loadedImage,
- buffer: !sharp && loadedImage.data,
- }
+ image: sharp && loadedImage,
+ buffer: !sharp && loadedImage.data,
+ }
: await getTransformedImage({
- src,
- image: loadedImage,
- config,
- type,
- });
+ src,
+ image: loadedImage,
+ config,
+ type,
+ });
imageObject = { hash, type, image, buffer };
@@ -184,8 +188,8 @@ export default async function load(id) {
const srcset =
sources.length > 1
? sources
- .map(({ width, modulePath }) => `${modulePath} ${width}w`)
- .join(", ")
+ .map(({ width, modulePath }) => `${modulePath} ${width}w`)
+ .join(", ")
: sources[0].modulePath;
return `export default "${srcset}"`;
diff --git a/packages/polymech/src/app/navigation.ts b/packages/polymech/src/app/navigation.ts
index 500f4a7..dc8fe1b 100644
--- a/packages/polymech/src/app/navigation.ts
+++ b/packages/polymech/src/app/navigation.ts
@@ -13,16 +13,16 @@ export const items = async (opts: { locale: string }) => {
"class": "hover:text-orange-600"
},
{
- "href": `/resources/home`,
+ "href": `/${opts.locale}/resources/home`,
"title": _T("Resources"),
"ariaLabel": "Resources",
"class": "hover:text-orange-600"
}
]
}
-export const footer_left = async ( locale: string ) => {
+export const footer_left = async (locale: string) => {
const _T = async (text: string) => await translate(text, I18N_SOURCE_LANGUAGE, locale)
- return await pMap(config.footer_left, async (item:any) => {
+ return await pMap(config.footer_left, async (item: any) => {
return {
"href": `${item.href}`,
"title": await _T(item.text),
@@ -31,9 +31,9 @@ export const footer_left = async ( locale: string ) => {
}
});
}
-export const footer_right = async ( locale: string ) => {
+export const footer_right = async (locale: string) => {
const _T = async (text: string) => await translate(text, I18N_SOURCE_LANGUAGE, locale)
- return await pMap(config.footer_right, async (item:any) => {
+ return await pMap(config.footer_right, async (item: any) => {
return {
"href": `/${item.href}`,
"title": await _T(item.text),
diff --git a/packages/polymech/src/components/Breadcrumb.astro b/packages/polymech/src/components/Breadcrumb.astro
index c1bb8c4..fd131f2 100644
--- a/packages/polymech/src/components/Breadcrumb.astro
+++ b/packages/polymech/src/components/Breadcrumb.astro
@@ -7,48 +7,53 @@ interface Props {
showHome?: boolean;
}
-const {
- currentPath,
- collection = '',
- title = '',
- separator = '/',
- showHome = true
+const {
+ currentPath,
+ collection = "",
+ title = "",
+ separator = "/",
+ showHome = true,
} = Astro.props;
// Parse the current path to generate breadcrumb items
-function generateBreadcrumbs(path: string, collection: string, pageTitle?: string) {
- const segments = path.split('/').filter(segment => segment !== '');
- const breadcrumbs: Array<{ label: string; href?: string; isLast?: boolean }> = [];
-
+function generateBreadcrumbs(
+ path: string,
+ collection: string,
+ pageTitle?: string,
+) {
+ const segments = path.split("/").filter((segment) => segment !== "");
+ const breadcrumbs: Array<{ label: string; href?: string; isLast?: boolean }> =
+ [];
+
// Add home if enabled
if (showHome) {
- breadcrumbs.push({ label: 'Home', href: '/' });
+ breadcrumbs.push({ label: "Home", href: "/" });
}
-
+
// Build path segments
- let currentHref = '';
+ let currentHref = "";
segments.forEach((segment, index) => {
currentHref += `/${segment}`;
const isLast = index === segments.length - 1;
-
+
// Format segment label
let label = segment
- .split('-')
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
- .join(' ');
-
+ .split("-")
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
+ .join(" ");
+
// Use page title for the last segment if provided
if (isLast && pageTitle) {
label = pageTitle;
}
-
+
breadcrumbs.push({
label,
- href: isLast ? undefined : currentHref + '/',
- isLast
+ href: isLast ? undefined : currentHref + "/",
+ isLast,
});
});
-
+
return breadcrumbs;
}
@@ -57,28 +62,30 @@ const breadcrumbs = generateBreadcrumbs(currentPath, collection, title);
@@ -88,7 +95,7 @@ const breadcrumbs = generateBreadcrumbs(currentPath, collection, title);
padding: 0.75rem 0;
border-bottom: 1px solid #e5e7eb;
}
-
+
.breadcrumb-list {
display: flex;
flex-wrap: wrap;
@@ -98,58 +105,57 @@ const breadcrumbs = generateBreadcrumbs(currentPath, collection, title);
padding: 0;
list-style: none;
}
-
+
.breadcrumb-item {
display: flex;
align-items: center;
gap: 0.25rem;
+ text-transform: uppercase;
}
-
+
.breadcrumb-link {
-
text-decoration: none;
font-size: 0.875rem;
padding: 0.25rem 0.5rem;
border-radius: 0.375rem;
transition: all 0.2s ease;
}
-
+
.breadcrumb-link:hover {
color: #374151;
background-color: #f3f4f6;
text-decoration: underline;
}
-
+
.breadcrumb-link:focus {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
-
+
.breadcrumb-current {
-
font-weight: 500;
font-size: 0.875rem;
padding: 0.25rem 0.5rem;
}
-
+
.breadcrumb-separator {
color: #9ca3af;
font-size: 0.875rem;
user-select: none;
}
-
+
/* Mobile responsiveness */
@media (max-width: 640px) {
.breadcrumb {
padding: 0.5rem 0;
}
-
+
.breadcrumb-link,
.breadcrumb-current {
font-size: 0.8125rem;
padding: 0.125rem 0.25rem;
}
-
+
.breadcrumb-list {
gap: 0.125rem;
}
diff --git a/packages/polymech/src/components/GalleryK.astro b/packages/polymech/src/components/GalleryK.astro
index 166f55c..bfd1c25 100644
--- a/packages/polymech/src/components/GalleryK.astro
+++ b/packages/polymech/src/components/GalleryK.astro
@@ -10,6 +10,7 @@ interface Image {
src: string
title?: string
description?: string
+ hash?: string
}
export interface Props {
@@ -29,9 +30,10 @@ export interface Props {
SHOW_TITLE?: boolean;
SHOW_DESCRIPTION?: boolean;
};
+ s?: string;
}
-const { images, gallerySettings = {}, lightboxSettings = {} } = Astro.props;
+const { images, gallerySettings = {}, lightboxSettings = {}, s } = Astro.props;
const mergedGallerySettings = {
SIZES_REGULAR: gallerySettings.SIZES_REGULAR || IMAGE_SETTINGS.GALLERY.SIZES_REGULAR,
@@ -46,7 +48,7 @@ const mergedLightboxSettings = {
SHOW_DESCRIPTION: lightboxSettings.SHOW_DESCRIPTION ?? IMAGE_SETTINGS.LIGHTBOX.SHOW_DESCRIPTION,
};
const locale = Astro.currentLocale || "en";
-console.log(`LGallery Images`, images)
+
---
))}
@@ -208,6 +213,8 @@ console.log(`LGallery Images`, images)
format="avif"
objectFit="contain"
sizes={IMAGE_SETTINGS.LIGHTBOX.SIZES_LARGE}
+ sizes={IMAGE_SETTINGS.LIGHTBOX.SIZES_LARGE}
+ s={s || image.hash}
attributes={{
img: { class: "max-w-[90vw] max-h-[90vh] object-contain rounded-lg lightbox-main" }
}}
diff --git a/packages/polymech/src/components/global/Footer.astro b/packages/polymech/src/components/global/Footer.astro
index 8ad237d..463e378 100644
--- a/packages/polymech/src/components/global/Footer.astro
+++ b/packages/polymech/src/components/global/Footer.astro
@@ -2,10 +2,7 @@
import Wrapper from "@/components/containers/Wrapper.astro";
import { footer_left, footer_right } from "@/app/navigation.js";
import { ISO_LANGUAGE_LABELS } from "@polymech/i18n";
-import {
- LANGUAGES_PROD,
- I18N_SOURCE_LANGUAGE,
-} from "config/config.js";
+import { LANGUAGES_PROD, I18N_SOURCE_LANGUAGE } from "config/config.js";
const locale = Astro.currentLocale || I18N_SOURCE_LANGUAGE;
const currentUrl = new URL(Astro.url);
@@ -16,7 +13,7 @@ const currentUrl = new URL(Astro.url);
* @returns {string[]} The URL path segments without the language code (if present).
*/
const getCleanPathSegments = (url) => {
- const segments = url.pathname.split('/').filter(Boolean);
+ const segments = url.pathname.split("/").filter(Boolean);
if (segments.length && LANGUAGES_PROD.includes(segments[0])) {
segments.shift();
}
@@ -32,33 +29,32 @@ const getCleanPathSegments = (url) => {
const buildLocalizedUrl = (lang, segments) => {
const newUrl = new URL(Astro.url);
// Prepend the language code and join with existing segments, removing any trailing slash.
- newUrl.pathname = `/${lang}/${segments.join('/')}`.replace(/\/+$/, '');
+ newUrl.pathname = `/${lang}/${segments.join("/")}`.replace(/\/+$/, "");
return newUrl.toString();
};
const cleanSegments = getCleanPathSegments(currentUrl);
-const languages = LANGUAGES_PROD.filter(
- (lang) => lang !== locale,
-).map((lang) => ({
- lang: ISO_LANGUAGE_LABELS[lang] || lang,
- url: buildLocalizedUrl(lang, cleanSegments),
-}));
+const languages = LANGUAGES_PROD.filter((lang) => lang !== locale).map(
+ (lang) => ({
+ lang: ISO_LANGUAGE_LABELS[lang] || lang,
+ url: buildLocalizedUrl(lang, cleanSegments),
+ }),
+);
const footerLeft = await footer_left(locale);
const footerRight = await footer_right(locale);
-
---