latest | cache bust 1/2

This commit is contained in:
babayaga 2025-12-24 11:47:18 +01:00
parent ac520a6e65
commit ecde90f89d
30 changed files with 261 additions and 572 deletions

View File

@ -1,8 +0,0 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

@ -1,14 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [
"@domain-expansion-test/*",
"docs"
]
}

View File

@ -1,19 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"dependencyDashboard": true,
"lockFileMaintenance": {
"enabled": true
},
"postUpdateOptions": ["pnpmDedupe"],
"packageRules": [
{
"groupName": "all dependencies",
"groupSlug": "all",
"matchPackagePatterns": ["*"],
"schedule": ["before 4am on Monday"],
"rangeStrategy": "bump"
}
],
"ignoreDeps": ["node"]
}

View File

@ -1,53 +0,0 @@
name: Surface PR Changesets
on: pull_request
permissions:
pull-requests: write
checks: write
statuses: write
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get changed files in the .changeset folder
id: changed-files
uses: tj-actions/changed-files@v35
with:
files: |
.changeset/**/*.md
- name: Check if any changesets contain minor or major changes
id: check
run: |
echo "Checking for changesets marked as minor or major"
echo "found=false" >> $GITHUB_OUTPUT
regex="[\"']astro[\"']: (minor|major)"
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
if [[ $(cat $file) =~ $regex ]]; then
version="${BASH_REMATCH[1]}"
echo "version=$version" >> $GITHUB_OUTPUT
echo "found=true" >> $GITHUB_OUTPUT
echo "$file has a $version release tag"
fi
done
- name: Add label
uses: actions/github-script@v6
if: steps.check.outputs.found == 'true'
env:
issue_number: ${{ github.event.number }}
with:
script: |
github.rest.issues.addLabels({
issue_number: process.env.issue_number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['semver: ${{ steps.check.outputs.version }}']
});

View File

@ -1,147 +0,0 @@
name: CI
on:
workflow_dispatch:
push:
branches:
- main
merge_group:
pull_request:
paths-ignore:
- "**/*.md"
- ".github/ISSUE_TEMPLATE/**"
# Automatically cancel older in-progress jobs on the same branch
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request_target' && github.head_ref || github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
env:
FORCE_COLOR: true
ASTRO_TELEMETRY_DISABLED: true
# 7 GiB by default on GitHub, setting to 6 GiB
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
NODE_OPTIONS: --max-old-space-size=6144
jobs:
# Build primes out Turbo build cache and pnpm cache
build:
name: "Build - Node ${{ matrix.NODE_VERSION }}"
runs-on: ubuntu-latest
timeout-minutes: 3
strategy:
matrix:
NODE_VERSION: [20, 22]
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PNPM
uses: pnpm/action-setup@v2
- name: Setup node@${{ matrix.NODE_VERSION }}
uses: actions/setup-node@main
with:
node-version: ${{ matrix.NODE_VERSION }}
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Build Packages
run: pnpm run package:build
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 5
needs: build
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PNPM
uses: pnpm/action-setup@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Format Check
run: pnpm run lint
test:
name: "Test: Node ${{ matrix.NODE_VERSION }}"
runs-on: ubuntu-latest
timeout-minutes: 10
needs: build
strategy:
matrix:
NODE_VERSION: [20, 22]
fail-fast: false
env:
NODE_VERSION: ${{ matrix.NODE_VERSION }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache turbo build setup
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-${{ matrix.NODE_VERSION }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ matrix.NODE_VERSION }}-turbo-
- name: Setup PNPM
uses: pnpm/action-setup@v2
- name: Setup node@${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.NODE_VERSION }}
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Build Packages
run: pnpm run package:build
- name: Test
run: pnpm test
working-directory: package
duplicated-packages:
name: Check for duplicated dependencies
runs-on: ubuntu-latest
env:
NODE_VERSION: 22
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PNPM
uses: pnpm/action-setup@v2
- name: Setup node@${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.NODE_VERSION }}
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Check duplicated dependencies
run: pnpm dedupe --prefer-offline --check

View File

@ -1,50 +0,0 @@
name: Preview mode
on:
pull_request:
types:
- synchronize
- opened
- reopened
env:
FORCE_COLOR: true
jobs:
no-preview:
name: Block Preview mode
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Setup PNPM
uses: pnpm/action-setup@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Check for preview mode
# Fails if in preview mode
run: pnpm changeset pre enter foo
- name: Remove Preview Label
uses: actions-ecosystem/action-remove-labels@v1
with:
labels: preview
- name: Add Label
if: ${{ failure() }}
uses: actions-ecosystem/action-add-labels@v1
with:
labels: preview

View File

@ -1,68 +0,0 @@
name: Release
on:
push:
branches:
- main
pull_request:
types:
- opened
- reopened
- synchronize
- labeled
defaults:
run:
shell: bash
env:
FORCE_COLOR: true
jobs:
changelog:
name: Changelog PR or Release
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'preview') }}
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Setup PNPM
uses: pnpm/action-setup@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: pnpm install
- name: Build Packages
run: pnpm run package:build
- name: Publish preview
if: ${{ contains(github.event.pull_request.labels.*.name, 'preview') }}
run: pnpm exec changeset publish
env:
# Use Node auth from above
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create Release Pull Request or Publish
id: changesets
if: ${{ github.event_name == 'push' }}
uses: changesets/action@v1
with:
# Note: pnpm install after versioning is necessary to refresh lockfile
version: pnpm run version
publish: pnpm exec changeset publish
commit: '[ci] release'
title: '[ci] release'
env:
GITHUB_TOKEN: ${{ secrets.COMMIT_TOKEN }}
# Needs access to publish to npm
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -1,25 +0,0 @@
name: TODO Tracking
on:
push:
# branches: [main]
permissions:
issues: read
repository-projects: read
contents: read
jobs:
track-todos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tdg-github-action
uses: ribtoks/tdg-github-action@master
with:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
SHA: ${{ github.sha }}
REF: ${{ github.ref }}
DRY_RUN: false
COMMENT_ON_ISSUES: true

View File

@ -1,3 +0,0 @@
#!/bin/sh
node_modules/.bin/lint-staged

View File

@ -1,4 +0,0 @@
{
"editor.defaultFormatter": "prettier",
"editor.gotoLocation.multipleDefinitions": "goto"
}

View File

@ -343,6 +343,13 @@
"fs-extra": "^8.1.0"
}
},
"node_modules/@manypkg/find-root/node_modules/@types/node": {
"version": "12.20.55",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@manypkg/find-root/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
@ -434,11 +441,16 @@
}
},
"node_modules/@types/node": {
"version": "12.20.55",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
"version": "24.3.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.3.tgz",
"integrity": "sha512-GKBNHjoNw3Kra1Qg5UXttsY5kiWMEfoHq2TmXb+b1rcm6N7B3wTrFYIf/oSZ1xNQ+hVVijgLkiDZh7jRRsh+Gw==",
"dev": true,
"license": "MIT"
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"undici-types": "~7.10.0"
}
},
"node_modules/ansi-colors": {
"version": "4.1.3",
@ -1920,6 +1932,15 @@
"node": ">=8.0"
}
},
"node_modules/undici-types": {
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",

View File

@ -20,26 +20,18 @@ export default async function load(id) {
const { search, searchParams } = fileURL;
id = id.replace(search, "");
id = id.replace(search, "");
const ext = path.extname(id).slice(1);
if (!supportedImageTypes.includes(ext)) return null;
debugger
const { default: astroViteConfigs } = await import(
// @ts-ignore
"../../astroViteConfigs.js"
);
const { environment, projectBase, assetFileNames } = astroViteConfigs;
const src = await getSrcPath(id);
const rootRelativePosixSrc = path.posix.normalize(
path.relative("", src).split(path.sep).join(path.posix.sep)
);
const getHash = (width) =>
objectHash(
{ width, options, rootRelativePosixSrc },

View File

@ -50,7 +50,7 @@ export default async function ({
imageWidth,
imageHeight,
imageFormat
} = await getProcessedImage(src, transformConfigs, { skipCache: true });
} = await getProcessedImage(src, transformConfigs, { skipCache: false });
src = path;
@ -114,9 +114,7 @@ export default async function ({
return returnObject;
} catch (error) {
console.error(`Error processing images:: ${src}`, error, error.stack);
debugger
console.error(`Error processing images:: ${src}`, error, error.stack);
throw error;
}
}

View File

@ -55,12 +55,18 @@ export default async function getProcessedImage(
let path = src.replace(/\\/g, `/`);
// @todo : remove this
let imagePath = isRemote ? join(cwd, path) : await getSrcPath(src);
if(!existsSync(imagePath)) {
console.log("getProcessedImage::imagePath does not exist", imagePath);
return {
path,
base,
rest,
};
}
const imageBuffer = await fs.readFile(imagePath);
const { image, imageWidth, imageHeight, imageFormat } =
await getImageDetails(imageBuffer, width, height, aspect, skipCache);
const { image, imageWidth, imageHeight, imageFormat } = await getImageDetails(imageBuffer, width, height, aspect);
return {
path,

View File

@ -27,9 +27,9 @@ export default async function getSrcset(
: "";
const [cleanSrc] = src.split("?");
const id = `${cleanSrc}?${params.slice(1)}`;
const fullPath = await getSrcPath(cleanSrc);
// @todo : remove this
const fullPath = await getSrcPath(id);
const { default: load } = await import("../../plugin/hooks/load.js");
// @ts-ignore
let srcset = null

View File

@ -1,12 +1,12 @@
export default {
"environment": "build",
"environment": "dev",
"isSsrBuild": false,
"projectBase": "",
"publicDir": "C:\\Users\\zx\\Desktop\\polymech\\site-min\\public\\",
"rootDir": "C:\\Users\\zx\\Desktop\\polymech\\site-min\\",
"mode": "production",
"outDir": "C:\\Users\\zx\\Desktop\\polymech\\site-min\\dist\\",
"assetsDir": "_astro",
"publicDir": "C:\\Users\\zx\\Desktop\\polymech\\site2\\public\\",
"rootDir": "C:\\Users\\zx\\Desktop\\polymech\\site2\\",
"mode": "dev",
"outDir": "dist",
"assetsDir": "/_astro",
"sourcemap": false,
"assetFileNames": "/_astro/[name]@[width].[hash][extname]"
}

View File

@ -2,9 +2,18 @@
import renderImg from "../api/renderImg.js";
import type { ImgConfigOptions } from "../types.d";
declare interface Props extends ImgConfigOptions {}
declare interface Props extends ImgConfigOptions {
s?: string;
}
const { link, style, img } = await renderImg(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, img } = await renderImg(rest);
---
<Fragment set:html={link + style + img} />

View File

@ -55,11 +55,11 @@ export default {
);
const { mode, outDir, assetsDir, isSsrBuild } = astroViteConfigs;
if (mode === "production") {
// 1. Define the manifest path in the current working directory.
const manifestPath = path.join(process.cwd(), "./imagetools-manifest.json")
// 2. Read the manifest from the previous build, if it exists.
let previousAssets = new Map();
@ -78,7 +78,7 @@ export default {
// 4. Merge previous and current assets. Current assets overwrite previous ones.
const allAssets = new Map([...previousAssets, ...currentAssets]);
logger.info(`[imagetools] Replaying ${allAssets.size} image(s) from the persistent manifest.`);
if(allAssets.size === 0) {
if (allAssets.size === 0) {
logger.info('[imagetools] No images to process.');
console.timeEnd('[imagetools] build:done');
return;
@ -88,6 +88,7 @@ export default {
await pMap(
[...allAssets.entries()],
async ([assetPath, { hash, image, buffer }]) => {
logger.info(`[imagetools] Processing image ${assetPath}...`);
try {
await saveAndCopyAsset(
hash,
@ -103,7 +104,7 @@ export default {
}
},
// higher concurrency causes sharp/vips errors as well
{ concurrency: 1 }
{ concurrency: 10 }
);
// 6. Write the updated asset list back to the manifest for the next build.
@ -116,11 +117,11 @@ export default {
return [key, { hash, buffer }];
}
);
const manifestContent = JSON.stringify(serializableAssets,null,2);
const manifestContent = JSON.stringify(serializableAssets, null, 2);
logger.info(`[imagetools] Writing manifest (${(manifestContent.length / 1024).toFixed(2)} KB)...`);
console.time('[imagetools] Manifest write took');
await writeFile(manifestPath, manifestContent);
await writeFile(manifestPath, manifestContent);
console.timeEnd('[imagetools] Manifest write took');
} catch (error) {
logger.error("Failed to write the asset manifest.");

View File

@ -14,8 +14,7 @@
"scripts": {
"test:watch": "vitest",
"test": "vitest run",
"test:src": "vitest run tests/src.js",
"test:image": "vitest run tests/image.js"
"test:src": "vitest run tests/src.js"
},
"repository": {
"type": "git",

View File

@ -106,6 +106,7 @@ export default async function load(id) {
config,
type,
})
const dataUri = `data:${type};base64,${(
buffer || (await getCachedBuffer(hash, image))
).toString("base64")}`

View File

@ -8,7 +8,6 @@ export async function getCachedBuffer(hash, image) {
return fs.promises.readFile(cacheFilePath);
}
const buffer = await image.clone().toBuffer();
console.log(`write ${cacheFilePath}`);
await fs.promises.writeFile(cacheFilePath, buffer);
return buffer;
}

View File

@ -85,12 +85,17 @@ fs.existsSync(fsCachePath) || fs.mkdirSync(fsCachePath, { recursive: true });
const cache_dir = () => {
if (!GlobalConfigOptions.cacheRoot) return false;
const dir = path.resolve(resolve(GlobalConfigOptions.cacheRoot, false, {
"POLYMECH-CACHE": get_var("POLYMECH-CACHE")
}))
if (!exists(dir)) {
mkdir(dir);
}
let dir;
if (!exists(GlobalConfigOptions.cacheRoot)) {
dir = path.resolve(resolve(GlobalConfigOptions.cacheRoot, false, {
"POLYMECH-CACHE": get_var("POLYMECH-CACHE")
}))
if (!exists(dir)) {
mkdir(dir);
}
}else{
dir = GlobalConfigOptions.cacheRoot
}
return dir + "/";
}
// @todo : cache dir

View File

@ -12,6 +12,7 @@
"./base/*": "./dist/base/*",
"./model/*": "./dist/model/*",
"./config/*": "./dist/config/*",
"./utils/*": "./dist/utils/*",
"./pages/*": "./src/pages/*",
"./routes/*": "./src/routes/*",
"./components/*": "./src/components/*",

View File

@ -491,7 +491,7 @@ export async function generateBreadcrumbs(
const segmentPath = arr.slice(0, index + 1).join('/');
const label = segment
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
breadcrumbs.push({

View File

@ -34,7 +34,7 @@ function generateBreadcrumbs(path: string, collection: string, pageTitle?: strin
// Format segment label
let label = segment
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
// Use page title for the last segment if provided

View File

@ -1,5 +1,5 @@
---
import { Img, Picture } from "imagetools/components";
import { Img } from "imagetools/components";
import Translate from "./i18n.astro"
import { translate } from "@/base/i18n";
@ -46,7 +46,7 @@ const mergedLightboxSettings = {
SHOW_DESCRIPTION: lightboxSettings.SHOW_DESCRIPTION ?? IMAGE_SETTINGS.LIGHTBOX.SHOW_DESCRIPTION,
};
const locale = Astro.currentLocale || "en";
console.log(`LGallery Images`, images)
---
<div
@ -61,6 +61,8 @@ const locale = Astro.currentLocale || "en";
minSwipeDistance: 50,
isSwiping: false,
images: ${JSON.stringify(images)},
lastTapTime: 0,
doubleTapDelay: 300,
handleSwipe() {
if (!this.isSwiping) return;
const swipeDistance = this.touchEndX - this.touchStartX;
@ -84,6 +86,27 @@ const locale = Astro.currentLocale || "en";
this.lightboxLoaded = true;
this.open = true;
};
},
preloadImage(index) {
// Preload without affecting lightboxLoaded state (for navigation within lightbox)
let img = new Image();
img.src = this.images[index].src;
// No need to wait for load when navigating within lightbox
},
handleThumbnailClick(index) {
const currentTime = Date.now();
const timeDiff = currentTime - this.lastTapTime;
if (timeDiff < this.doubleTapDelay) {
// Double tap/click detected - open lightbox
this.currentIndex = index;
this.preloadAndOpen();
} else {
// Single tap/click - just change current image
this.currentIndex = index;
}
this.lastTapTime = currentTime;
}
}
`}
@ -91,15 +114,13 @@ const locale = Astro.currentLocale || "en";
@keydown.window="if(open){
if($event.key === 'ArrowRight' && currentIndex < total - 1){
currentIndex++;
lightboxLoaded = false;
preloadAndOpen();
preloadImage(currentIndex);
} else if($event.key === 'ArrowLeft' && currentIndex > 0){
currentIndex--;
lightboxLoaded = false;
preloadAndOpen();
preloadImage(currentIndex);
}
}"
class="product-gallery"><div class="flex flex-col h-full">
class="product-gallery bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden"><div class="flex flex-col h-full p-4">
<!-- Main Image (with swipe functionality) -->
<div
class="flex-1 flex items-center justify-center cursor-pointer rounded-lg"
@ -113,7 +134,7 @@ const locale = Astro.currentLocale || "en";
<Img
src={image.src}
alt={image.alt,I18N_SOURCE_LANGUAGE,locale}
objectFit="cover"
objectFit="contain"
format="avif"
placeholder="blurred"
sizes={mergedGallerySettings.SIZES_REGULAR}
@ -125,29 +146,29 @@ const locale = Astro.currentLocale || "en";
))}
</div>
<!-- Image Info -->
{ (mergedGallerySettings.SHOW_TITLE || mergedGallerySettings.SHOW_DESCRIPTION) && (
<div class="text-center py-4">
{ (images.some(img => (mergedGallerySettings.SHOW_TITLE && img.title && img.title.trim().length > 0) || (mergedGallerySettings.SHOW_DESCRIPTION && img.description && img.description.trim().length > 0))) && (
<div class="text-center py-4 border-t border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700/50 -mx-4 px-4">
{images.map((image, index) => (
<div x-show={`currentIndex === ${index}`} key={index}>
{ mergedGallerySettings.SHOW_TITLE && ( <h2 id="imageTitle" class="text-xl font-bold">{image.title}</h2>)}
{ mergedGallerySettings.SHOW_DESCRIPTION && (<p id="imageDescription" class="text-gray-600"><Translate>{ image.description}</Translate></p>)}
{ mergedGallerySettings.SHOW_TITLE && image.title && image.title.trim().length > 0 && ( <h2 id="imageTitle" class="text-xl font-bold text-gray-900 dark:text-white">{image.title}</h2>)}
{ mergedGallerySettings.SHOW_DESCRIPTION && image.description && image.description.trim().length > 0 && (<p id="imageDescription" class="text-gray-600 dark:text-gray-300"><Translate>{ image.description}</Translate></p>)}
</div>
))}
</div>
)}
<!-- Thumbnails -->
<div class="overflow-x-auto scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-gray-200">
<div class="grid grid-cols-3 md:grid-cols-4 gap-2 p-2 mt-2 ml-2 mr-2 items-center justify-center">
<div class="overflow-x-auto scrollbar-thin scrollbar-thumb-gray-400 dark:scrollbar-thumb-gray-500 scrollbar-track-gray-200 dark:scrollbar-track-gray-700 border-t border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-700/30 -mx-4 px-4 pt-3">
<div class="grid grid-cols-3 md:grid-cols-4 gap-2 pb-2 items-center justify-center">
{images.map((image, index) => (
<button
key={index}
x-on:click={`currentIndex = ${index};`}
x-on:click={`handleThumbnailClick(${index})`}
:class={`currentIndex === ${index} ? 'ring-2 ring-orange-500' : ''`}
class="thumbnail thumbnail-btn rounded-lg"
>
<Img
src={image.src}
objectFit="cover"
objectFit="contain"
format="avif"
placeholder="blurred"
sizes={mergedGallerySettings.SIZES_THUMB}
@ -191,10 +212,10 @@ const locale = Astro.currentLocale || "en";
img: { class: "max-w-[90vw] max-h-[90vh] object-contain rounded-lg lightbox-main" }
}}
/>
{ (mergedLightboxSettings.SHOW_TITLE || mergedLightboxSettings.SHOW_DESCRIPTION) && (
{ ((mergedLightboxSettings.SHOW_TITLE && image.title && image.title.trim().length > 0) || (mergedLightboxSettings.SHOW_DESCRIPTION && image.description && image.description.trim().length > 0)) && (
<div class="absolute bottom-0 left-1/2 transform -translate-x-1/2 m-[8px] max-h-[32vh] p-2 text-white bg-black/50 rounded-lg" style="width: 90%;">
{ mergedLightboxSettings.SHOW_TITLE && ( <h3 class="text-xl"><Translate>{image.title}</Translate></h3>)}
{ mergedLightboxSettings.SHOW_DESCRIPTION && (<p><Translate>{image.description}</Translate></p>)} </div>
{ mergedLightboxSettings.SHOW_TITLE && image.title && image.title.trim().length > 0 && ( <h3 class="text-xl"><Translate>{image.title}</Translate></h3>)}
{ mergedLightboxSettings.SHOW_DESCRIPTION && image.description && image.description.trim().length > 0 && (<p><Translate>{image.description}</Translate></p>)} </div>
)}
</div>
))}
@ -209,7 +230,7 @@ const locale = Astro.currentLocale || "en";
<!-- Navigation Buttons -->
<button
x-show="currentIndex > 0"
x-on:click="currentIndex--;"
x-on:click="currentIndex--; preloadImage(currentIndex);"
class="absolute left-0 top-1/2 transform -translate-y-1/2 p-4 m-[8px] text-white text-3xl bg-gray-800/75 bg-opacity-75 rounded-lg lightbox-nav"
aria-label="Previous"
>
@ -217,7 +238,7 @@ const locale = Astro.currentLocale || "en";
</button>
<button
x-show="currentIndex < total - 1"
x-on:click="currentIndex++; "
x-on:click="currentIndex++; preloadImage(currentIndex);"
class="absolute right-0 top-1/2 transform -translate-y-1/2 p-4 m-[8px] text-white text-3xl bg-gray-800/75 bg-opacity-75 rounded-lg lightbox-nav"
aria-label="Next"
>

View File

@ -1,96 +1,134 @@
---
import { Img } from "imagetools/components";
import { resolveImagePath } from '@/utils/path-resolution';
export interface Props {
src: string;
alt: string;
width?: number;
height?: number;
class?: string;
entryPath?: string;
lightbox?: boolean;
[key: string]: any;
}
const {
src,
alt,
width,
height,
format,
placeholder,
objectFit,
loading,
sizes,
class: className,
entryPath,
lightbox = true,
...rest
} = Astro.props;
const resolvedSrc = resolveImagePath(src, entryPath, Astro.url);
const imgProps = { alt, width, height, format, placeholder, objectFit, loading, sizes };
Object.keys(imgProps).forEach(key => imgProps[key] === undefined && delete imgProps[key]);
---
<button
class:list={["w-full h-full", { 'lightbox-enabled': lightbox }]}
>
<Img src={resolvedSrc} {...imgProps} attributes={{
img: {
...(className && { class: className })
}
}} />
</button>
<script define:vars={{ lightbox }}>
const button = document.currentScript.previousElementSibling;
function getHighestResSrc(img) {
let highestResSrc = img.src;
const srcset = img.getAttribute('srcset');
if (srcset) {
let maxWidth = 0;
srcset.split(',').forEach(s => {
const parts = s.trim().split(/\s+/);
const url = parts[0];
const widthStr = parts[1];
if (widthStr && widthStr.endsWith('w')) {
const width = parseInt(widthStr.slice(0, -1), 10);
if (width > maxWidth) {
maxWidth = width;
highestResSrc = url;
}
}
});
}
return highestResSrc;
}
button.addEventListener('click', (event) => {
if (!lightbox) return;
const triggerImage = event.currentTarget.querySelector('img');
if (!triggerImage) return;
const allImages = Array.from(document.querySelectorAll('.lightbox-enabled img')).map(img => ({
src: getHighestResSrc(img),
alt: img.getAttribute('alt')
}));
const triggerSrc = getHighestResSrc(triggerImage);
const currentIndex = allImages.findIndex(img => img.src === triggerSrc);
if (currentIndex === -1) return;
const lightboxEvent = new CustomEvent('open-lightbox', {
detail: {
images: allImages,
currentIndex: currentIndex
}
});
window.dispatchEvent(lightboxEvent);
});
</script>
---
import { Img } from "imagetools/components";
import { resolveImagePath } from '@/utils/path-resolution';
export interface Props {
src: string;
alt: string;
width?: number;
height?: number;
class?: string;
entryPath?: string;
lightbox?: boolean;
preset?: 'small' | 'medium' | 'large';
[key: string]: any;
}
const {
src,
alt,
width,
height,
format,
placeholder,
objectFit,
loading,
sizes,
class: className,
entryPath,
lightbox = true,
preset,
...rest
} = Astro.props;
const resolvedSrc = resolveImagePath(src, entryPath, Astro.url);
const imgProps = { alt, width, height, format, placeholder, objectFit, loading, sizes };
Object.keys(imgProps).forEach(key => imgProps[key] === undefined && delete imgProps[key]);
// Define preset styles
const presetStyles = {
small: "flex justify-center my-4 mx-auto w-[200px]",
medium: "flex justify-center my-4 mx-auto w-[600px]",
large: "flex justify-center my-4 mx-auto w-[800px]"
};
const containerClass = preset ? presetStyles[preset] : "w-full h-full";
const buttonClass = preset ? "w-full h-full" : "w-full h-full";
const imgClass = preset ? "w-full h-auto object-contain" : "";
---
{preset ? (
<div class={containerClass}>
<button
class:list={[buttonClass, { 'lightbox-enabled': lightbox }]}
>
<Img
src={resolvedSrc}
{...imgProps}
attributes={{
img: {
class: [imgClass, className].filter(Boolean).join(' ') || undefined
}
}
} />
</button>
</div>
) : (
<button
class:list={["w-full h-full", { 'lightbox-enabled': lightbox }]}
>
<Img
src={resolvedSrc}
{...imgProps}
attributes={{
img: {
...(className && { class: className })
}
}
} />
</button>
)}
<script define:vars={{ lightbox, preset }}>
const button = preset
? document.currentScript.previousElementSibling.querySelector('button')
: document.currentScript.previousElementSibling;
function getHighestResSrc(img) {
let highestResSrc = img.src;
const srcset = img.getAttribute('srcset');
if (srcset) {
let maxWidth = 0;
srcset.split(',').forEach(s => {
const parts = s.trim().split(/\s+/);
const url = parts[0];
const widthStr = parts[1];
if (widthStr && widthStr.endsWith('w')) {
const width = parseInt(widthStr.slice(0, -1), 10);
if (width > maxWidth) {
maxWidth = width;
highestResSrc = url;
}
}
});
}
return highestResSrc;
}
button.addEventListener('click', (event) => {
if (!lightbox) return;
const triggerImage = event.currentTarget.querySelector('img');
if (!triggerImage) return;
const allImages = Array.from(document.querySelectorAll('.lightbox-enabled img')).map(img => ({
src: getHighestResSrc(img),
alt: img.getAttribute('alt')
}));
const triggerSrc = getHighestResSrc(triggerImage);
const currentIndex = allImages.findIndex(img => img.src === triggerSrc);
if (currentIndex === -1) return;
const lightboxEvent = new CustomEvent('open-lightbox', {
detail: {
images: allImages,
currentIndex: currentIndex
}
});
window.dispatchEvent(lightboxEvent);
});
</script>

View File

@ -226,14 +226,7 @@ if (isPrevNext && entryPath) {
)}
</header>
{frontmatter.image?.url && (
<RelativeImage
src={frontmatter.image.url}
alt={frontmatter.image.alt || frontmatter.title}
class="w-full h-auto rounded-lg mb-8 object-cover"
entryPath={entryPath}
/>
)}
<div class="markdown-content">
<slot />

View File

@ -18,12 +18,9 @@ const paths = await getStaticPaths_fs(getCollection, collectionName, config.LANG
export async function getStaticPaths() {
const collectionName = 'resources';
const config = PolymechInstance.getConfig();
const paths = await getStaticPaths_fs(getCollection, collectionName, config.LANGUAGES_PROD, config.COLLECTION_FILTERS);
// console.log(`getStaticPaths ${PolymechInstance.languages} ${PolymechInstance.products}`, paths);
// Get configuration from registry (now has proper defaults)
//const paths = await getStaticPaths_fs(getCollection, collectionName, config.LANGUAGES_PROD, config.COLLECTION_FILTERS);
const languages = PolymechInstance.languages;
const products = PolymechInstance.products;
const products = PolymechInstance.products;
return languages.flatMap((lang: string) =>
products.map((product: string) => ({
params: { lang, slug: product }

View File

@ -67,8 +67,7 @@ export function resolveImagePath(src: string, entryPath?: string, astroUrl?: URL
// --- Strategy 2.2: Fallback to URL parsing ---
else if (astroUrl) {
strategy = 'URL';
// if (enableDebugErrors) console.warn(`[resolveImagePath] [INFO-URL] No entryPath provided. Falling back to URL-based resolution for "${src}". This is less reliable.`);
// if (enableDebugErrors) console.warn(`[resolveImagePath] [INFO-URL] No entryPath provided. Falling back to URL-based resolution for "${src}". This is less reliable.`);
const isFolderUrl = astroUrl.pathname.endsWith('/');
const pathSegments = astroUrl.pathname.split('/').filter(p => p);
const hasLocale = pathSegments.length > 0 && /^[a-z]{2}$/.test(pathSegments[0]);