perspectapi-ts-sdk 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -2
- package/dist/index.d.mts +143 -1
- package/dist/index.d.ts +143 -1
- package/dist/index.js +147 -3
- package/dist/index.mjs +140 -3
- package/package.json +1 -1
- package/src/client/products-client.ts +10 -3
- package/src/index.ts +16 -0
- package/src/types/index.ts +3 -0
- package/src/utils/image-transform.ts +296 -0
package/README.md
CHANGED
|
@@ -84,6 +84,51 @@ const checkout = await createCheckoutSession({
|
|
|
84
84
|
> 📚 See [docs/loaders.md](docs/loaders.md) for full walkthroughs, including fallback data, custom logging, and Stripe price resolution.
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
## Image Transformations
|
|
88
|
+
|
|
89
|
+
The SDK includes utilities for transforming images using Cloudflare's Image Resizing service:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { transformMediaItem, buildImageUrl } from 'perspectapi-ts-sdk';
|
|
93
|
+
|
|
94
|
+
// Get a product with media
|
|
95
|
+
const product = await client.products.getProduct('mysite', 123);
|
|
96
|
+
const media = product.data.media?.[0];
|
|
97
|
+
|
|
98
|
+
if (media) {
|
|
99
|
+
// Generate all responsive sizes automatically
|
|
100
|
+
const urls = transformMediaItem('https://api.perspect.co', media);
|
|
101
|
+
|
|
102
|
+
console.log(urls.thumbnail); // 150x150 cover crop
|
|
103
|
+
console.log(urls.small); // 400px wide
|
|
104
|
+
console.log(urls.medium); // 800px wide
|
|
105
|
+
console.log(urls.large); // 1200px wide
|
|
106
|
+
console.log(urls.original); // Original with auto format
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Or build custom transformations
|
|
110
|
+
const customUrl = buildImageUrl(
|
|
111
|
+
'https://api.perspect.co',
|
|
112
|
+
'media/mysite/photo.jpg',
|
|
113
|
+
{
|
|
114
|
+
width: 400,
|
|
115
|
+
height: 300,
|
|
116
|
+
fit: 'cover',
|
|
117
|
+
format: 'webp',
|
|
118
|
+
quality: 85
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Features:**
|
|
124
|
+
- ✅ On-the-fly image resizing (no pre-generated thumbnails)
|
|
125
|
+
- ✅ Automatic format optimization (WebP/AVIF)
|
|
126
|
+
- ✅ Responsive image support with srcset
|
|
127
|
+
- ✅ CDN caching at the edge
|
|
128
|
+
- ✅ Bandwidth savings
|
|
129
|
+
|
|
130
|
+
> 📚 See [docs/image-transforms.md](docs/image-transforms.md) for complete documentation, examples, and best practices.
|
|
131
|
+
|
|
87
132
|
## High-Level Data Loaders
|
|
88
133
|
|
|
89
134
|
The SDK now ships with convenience loaders that wrap the lower-level REST clients. They help you:
|
|
@@ -189,7 +234,8 @@ const content = await client.content.getContent('your-site-name', {
|
|
|
189
234
|
page: 1,
|
|
190
235
|
limit: 20,
|
|
191
236
|
page_status: 'publish',
|
|
192
|
-
page_type: 'post'
|
|
237
|
+
page_type: 'post',
|
|
238
|
+
slug_prefix: 'blog' // Optional: filter by slug prefix
|
|
193
239
|
});
|
|
194
240
|
|
|
195
241
|
// Get content by ID
|
|
@@ -229,7 +275,8 @@ await client.content.unpublishContent(123);
|
|
|
229
275
|
const products = await client.products.getProducts('my-site', {
|
|
230
276
|
page: 1,
|
|
231
277
|
limit: 20,
|
|
232
|
-
isActive: true
|
|
278
|
+
isActive: true,
|
|
279
|
+
slug_prefix: 'shop' // Optional: filter by slug prefix
|
|
233
280
|
});
|
|
234
281
|
|
|
235
282
|
// Create new product
|
|
@@ -605,6 +652,54 @@ const client = createPerspectApiClient({
|
|
|
605
652
|
});
|
|
606
653
|
```
|
|
607
654
|
|
|
655
|
+
## Slug Prefix Filtering
|
|
656
|
+
|
|
657
|
+
The SDK supports filtering content and products by `slug_prefix` to organize your content by URL structure:
|
|
658
|
+
|
|
659
|
+
```typescript
|
|
660
|
+
// Filter blog posts
|
|
661
|
+
const blogPosts = await client.content.getContent('mysite', {
|
|
662
|
+
slug_prefix: 'blog', // /blog/post-1, /blog/post-2
|
|
663
|
+
page_status: 'publish'
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// Filter news articles
|
|
667
|
+
const news = await client.content.getContent('mysite', {
|
|
668
|
+
slug_prefix: 'news', // /news/article-1, /news/article-2
|
|
669
|
+
page_status: 'publish'
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
// Filter shop products
|
|
673
|
+
const shopProducts = await client.products.getProducts('mysite', {
|
|
674
|
+
slug_prefix: 'shop', // /shop/product-1, /shop/product-2
|
|
675
|
+
isActive: true
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// Filter artwork products
|
|
679
|
+
const artwork = await client.products.getProducts('mysite', {
|
|
680
|
+
slug_prefix: 'artwork', // /artwork/painting-1, /artwork/sculpture-1
|
|
681
|
+
isActive: true
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// Combine with other filters
|
|
685
|
+
const results = await client.content.getContent('mysite', {
|
|
686
|
+
slug_prefix: 'blog',
|
|
687
|
+
page_status: 'publish',
|
|
688
|
+
page_type: 'post',
|
|
689
|
+
search: 'javascript', // Search within blog posts only
|
|
690
|
+
page: 1,
|
|
691
|
+
limit: 20
|
|
692
|
+
});
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
**Use Cases:**
|
|
696
|
+
- Organize blog posts, news, and documentation separately
|
|
697
|
+
- Separate shop products from artwork or digital downloads
|
|
698
|
+
- Create multi-section websites with distinct URL structures
|
|
699
|
+
- Filter content/products by section for navigation
|
|
700
|
+
|
|
701
|
+
**See `examples/slug-prefix-examples.ts` for 20+ real-world examples!**
|
|
702
|
+
|
|
608
703
|
## TypeScript Support
|
|
609
704
|
|
|
610
705
|
The SDK is written in TypeScript and provides comprehensive type definitions:
|
package/dist/index.d.mts
CHANGED
|
@@ -188,6 +188,7 @@ interface Content {
|
|
|
188
188
|
contentMarkdown?: string;
|
|
189
189
|
custom?: Record<string, any>;
|
|
190
190
|
slug?: string;
|
|
191
|
+
slug_prefix?: string;
|
|
191
192
|
pageStatus: ContentStatus;
|
|
192
193
|
pageType: ContentType;
|
|
193
194
|
userId?: number;
|
|
@@ -211,6 +212,7 @@ interface ContentQueryParams extends PaginationParams {
|
|
|
211
212
|
page_type?: ContentType;
|
|
212
213
|
search?: string;
|
|
213
214
|
user_id?: number;
|
|
215
|
+
slug_prefix?: string;
|
|
214
216
|
}
|
|
215
217
|
interface Category {
|
|
216
218
|
id: number;
|
|
@@ -277,6 +279,7 @@ interface ProductQueryParams extends PaginationParams {
|
|
|
277
279
|
search?: string;
|
|
278
280
|
category?: string | string[];
|
|
279
281
|
category_id?: number | string | Array<number | string>;
|
|
282
|
+
slug_prefix?: string;
|
|
280
283
|
}
|
|
281
284
|
interface BlogPost {
|
|
282
285
|
id: string | number;
|
|
@@ -1925,6 +1928,145 @@ declare class PerspectApiClient {
|
|
|
1925
1928
|
*/
|
|
1926
1929
|
declare function createPerspectApiClient(config: PerspectApiConfig): PerspectApiClient;
|
|
1927
1930
|
|
|
1931
|
+
/**
|
|
1932
|
+
* Cloudflare Image Resizing Integration
|
|
1933
|
+
* Transforms images on-the-fly using Cloudflare's Image Resizing service
|
|
1934
|
+
* https://developers.cloudflare.com/images/transform-images/transform-via-url/
|
|
1935
|
+
*/
|
|
1936
|
+
interface ImageTransformOptions {
|
|
1937
|
+
width?: number;
|
|
1938
|
+
height?: number;
|
|
1939
|
+
fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
|
|
1940
|
+
gravity?: 'auto' | 'left' | 'right' | 'top' | 'bottom' | 'center';
|
|
1941
|
+
quality?: number;
|
|
1942
|
+
format?: 'auto' | 'avif' | 'webp' | 'json' | 'jpeg' | 'png';
|
|
1943
|
+
sharpen?: number;
|
|
1944
|
+
blur?: number;
|
|
1945
|
+
rotate?: 0 | 90 | 180 | 270;
|
|
1946
|
+
dpr?: number;
|
|
1947
|
+
metadata?: 'keep' | 'copyright' | 'none';
|
|
1948
|
+
background?: string;
|
|
1949
|
+
trim?: {
|
|
1950
|
+
top?: number;
|
|
1951
|
+
right?: number;
|
|
1952
|
+
bottom?: number;
|
|
1953
|
+
left?: number;
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
interface ResponsiveImageSizes {
|
|
1957
|
+
thumbnail: ImageTransformOptions;
|
|
1958
|
+
small: ImageTransformOptions;
|
|
1959
|
+
medium: ImageTransformOptions;
|
|
1960
|
+
large: ImageTransformOptions;
|
|
1961
|
+
original: ImageTransformOptions;
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Default responsive image sizes
|
|
1965
|
+
*/
|
|
1966
|
+
declare const DEFAULT_IMAGE_SIZES: ResponsiveImageSizes;
|
|
1967
|
+
/**
|
|
1968
|
+
* Build Cloudflare Image Resizing URL
|
|
1969
|
+
*
|
|
1970
|
+
* @param baseUrl - The base URL of your API (e.g., "https://api.perspect.co")
|
|
1971
|
+
* @param mediaPath - The path to the media file (e.g., "media/site/image.jpg")
|
|
1972
|
+
* @param options - Transform options
|
|
1973
|
+
* @returns Cloudflare Image Resizing URL
|
|
1974
|
+
*
|
|
1975
|
+
* @example
|
|
1976
|
+
* ```typescript
|
|
1977
|
+
* const url = buildImageUrl(
|
|
1978
|
+
* 'https://api.perspect.co',
|
|
1979
|
+
* 'media/mysite/photo.jpg',
|
|
1980
|
+
* { width: 400, format: 'webp', quality: 85 }
|
|
1981
|
+
* );
|
|
1982
|
+
* // Returns: '/cdn-cgi/image/width=400,format=webp,quality=85/https://api.perspect.co/media/mysite/photo.jpg'
|
|
1983
|
+
* ```
|
|
1984
|
+
*/
|
|
1985
|
+
declare function buildImageUrl(baseUrl: string, mediaPath: string, options?: ImageTransformOptions): string;
|
|
1986
|
+
/**
|
|
1987
|
+
* Generate responsive image URLs for different sizes
|
|
1988
|
+
*
|
|
1989
|
+
* @example
|
|
1990
|
+
* ```typescript
|
|
1991
|
+
* const urls = generateResponsiveUrls(
|
|
1992
|
+
* 'https://api.perspect.co',
|
|
1993
|
+
* 'media/mysite/photo.jpg'
|
|
1994
|
+
* );
|
|
1995
|
+
* // Returns: { thumbnail: '...', small: '...', medium: '...', large: '...', original: '...' }
|
|
1996
|
+
* ```
|
|
1997
|
+
*/
|
|
1998
|
+
declare function generateResponsiveUrls(baseUrl: string, mediaPath: string, sizes?: ResponsiveImageSizes): Record<keyof ResponsiveImageSizes, string>;
|
|
1999
|
+
/**
|
|
2000
|
+
* Generate srcset for responsive images
|
|
2001
|
+
*
|
|
2002
|
+
* @example
|
|
2003
|
+
* ```typescript
|
|
2004
|
+
* const srcset = generateSrcSet(
|
|
2005
|
+
* 'https://api.perspect.co',
|
|
2006
|
+
* 'media/mysite/photo.jpg',
|
|
2007
|
+
* [400, 800, 1200]
|
|
2008
|
+
* );
|
|
2009
|
+
* // Returns: "...?width=400 400w, ...?width=800 800w, ...?width=1200 1200w"
|
|
2010
|
+
* ```
|
|
2011
|
+
*/
|
|
2012
|
+
declare function generateSrcSet(baseUrl: string, mediaPath: string, widths?: number[]): string;
|
|
2013
|
+
/**
|
|
2014
|
+
* Generate sizes attribute for responsive images
|
|
2015
|
+
*
|
|
2016
|
+
* @example
|
|
2017
|
+
* ```typescript
|
|
2018
|
+
* const sizes = generateSizesAttribute();
|
|
2019
|
+
* // Returns: "(max-width: 640px) 100vw, (max-width: 768px) 80vw, ..."
|
|
2020
|
+
* ```
|
|
2021
|
+
*/
|
|
2022
|
+
declare function generateSizesAttribute(breakpoints?: Array<{
|
|
2023
|
+
maxWidth: string;
|
|
2024
|
+
size: string;
|
|
2025
|
+
}>): string;
|
|
2026
|
+
/**
|
|
2027
|
+
* Helper to generate complete responsive image HTML
|
|
2028
|
+
*
|
|
2029
|
+
* @example
|
|
2030
|
+
* ```typescript
|
|
2031
|
+
* const html = generateResponsiveImageHtml(
|
|
2032
|
+
* 'https://api.perspect.co',
|
|
2033
|
+
* 'media/mysite/photo.jpg',
|
|
2034
|
+
* 'My photo',
|
|
2035
|
+
* { className: 'rounded-lg', loading: 'lazy' }
|
|
2036
|
+
* );
|
|
2037
|
+
* ```
|
|
2038
|
+
*/
|
|
2039
|
+
declare function generateResponsiveImageHtml(baseUrl: string, mediaPath: string, alt: string, options?: {
|
|
2040
|
+
className?: string;
|
|
2041
|
+
loading?: 'lazy' | 'eager';
|
|
2042
|
+
decoding?: 'async' | 'sync' | 'auto';
|
|
2043
|
+
sizes?: string;
|
|
2044
|
+
widths?: number[];
|
|
2045
|
+
}): string;
|
|
2046
|
+
/**
|
|
2047
|
+
* Transform a MediaItem into responsive URLs
|
|
2048
|
+
* Convenience function for working with MediaItem objects from the API
|
|
2049
|
+
*
|
|
2050
|
+
* @example
|
|
2051
|
+
* ```typescript
|
|
2052
|
+
* import { transformMediaItem } from 'perspectapi-ts-sdk';
|
|
2053
|
+
*
|
|
2054
|
+
* const product = await client.products.getProduct('mysite', 123);
|
|
2055
|
+
* const media = product.data.media?.[0];
|
|
2056
|
+
*
|
|
2057
|
+
* if (media) {
|
|
2058
|
+
* const urls = transformMediaItem('https://api.perspect.co', media);
|
|
2059
|
+
* console.log(urls.thumbnail); // Cloudflare-transformed thumbnail URL
|
|
2060
|
+
* }
|
|
2061
|
+
* ```
|
|
2062
|
+
*/
|
|
2063
|
+
declare function transformMediaItem(baseUrl: string, media: {
|
|
2064
|
+
link: string;
|
|
2065
|
+
} | {
|
|
2066
|
+
r2_key: string;
|
|
2067
|
+
site_name: string;
|
|
2068
|
+
}, sizes?: ResponsiveImageSizes): Record<keyof ResponsiveImageSizes, string>;
|
|
2069
|
+
|
|
1928
2070
|
/**
|
|
1929
2071
|
* High-level data loading helpers that wrap the PerspectAPI SDK clients.
|
|
1930
2072
|
* These helpers provide convenient product and content loading utilities with
|
|
@@ -2057,4 +2199,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
|
|
|
2057
2199
|
error: string;
|
|
2058
2200
|
}>;
|
|
2059
2201
|
|
|
2060
|
-
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, CategoriesClient, type Category, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, HttpClient, type HttpMethod, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductQueryParams, ProductsClient, type RequestOptions, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, type Webhook, WebhooksClient, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformProduct };
|
|
2202
|
+
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, CategoriesClient, type Category, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, DEFAULT_IMAGE_SIZES, HttpClient, type HttpMethod, type ImageTransformOptions, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductQueryParams, ProductsClient, type RequestOptions, type ResponsiveImageSizes, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
|
package/dist/index.d.ts
CHANGED
|
@@ -188,6 +188,7 @@ interface Content {
|
|
|
188
188
|
contentMarkdown?: string;
|
|
189
189
|
custom?: Record<string, any>;
|
|
190
190
|
slug?: string;
|
|
191
|
+
slug_prefix?: string;
|
|
191
192
|
pageStatus: ContentStatus;
|
|
192
193
|
pageType: ContentType;
|
|
193
194
|
userId?: number;
|
|
@@ -211,6 +212,7 @@ interface ContentQueryParams extends PaginationParams {
|
|
|
211
212
|
page_type?: ContentType;
|
|
212
213
|
search?: string;
|
|
213
214
|
user_id?: number;
|
|
215
|
+
slug_prefix?: string;
|
|
214
216
|
}
|
|
215
217
|
interface Category {
|
|
216
218
|
id: number;
|
|
@@ -277,6 +279,7 @@ interface ProductQueryParams extends PaginationParams {
|
|
|
277
279
|
search?: string;
|
|
278
280
|
category?: string | string[];
|
|
279
281
|
category_id?: number | string | Array<number | string>;
|
|
282
|
+
slug_prefix?: string;
|
|
280
283
|
}
|
|
281
284
|
interface BlogPost {
|
|
282
285
|
id: string | number;
|
|
@@ -1925,6 +1928,145 @@ declare class PerspectApiClient {
|
|
|
1925
1928
|
*/
|
|
1926
1929
|
declare function createPerspectApiClient(config: PerspectApiConfig): PerspectApiClient;
|
|
1927
1930
|
|
|
1931
|
+
/**
|
|
1932
|
+
* Cloudflare Image Resizing Integration
|
|
1933
|
+
* Transforms images on-the-fly using Cloudflare's Image Resizing service
|
|
1934
|
+
* https://developers.cloudflare.com/images/transform-images/transform-via-url/
|
|
1935
|
+
*/
|
|
1936
|
+
interface ImageTransformOptions {
|
|
1937
|
+
width?: number;
|
|
1938
|
+
height?: number;
|
|
1939
|
+
fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
|
|
1940
|
+
gravity?: 'auto' | 'left' | 'right' | 'top' | 'bottom' | 'center';
|
|
1941
|
+
quality?: number;
|
|
1942
|
+
format?: 'auto' | 'avif' | 'webp' | 'json' | 'jpeg' | 'png';
|
|
1943
|
+
sharpen?: number;
|
|
1944
|
+
blur?: number;
|
|
1945
|
+
rotate?: 0 | 90 | 180 | 270;
|
|
1946
|
+
dpr?: number;
|
|
1947
|
+
metadata?: 'keep' | 'copyright' | 'none';
|
|
1948
|
+
background?: string;
|
|
1949
|
+
trim?: {
|
|
1950
|
+
top?: number;
|
|
1951
|
+
right?: number;
|
|
1952
|
+
bottom?: number;
|
|
1953
|
+
left?: number;
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
interface ResponsiveImageSizes {
|
|
1957
|
+
thumbnail: ImageTransformOptions;
|
|
1958
|
+
small: ImageTransformOptions;
|
|
1959
|
+
medium: ImageTransformOptions;
|
|
1960
|
+
large: ImageTransformOptions;
|
|
1961
|
+
original: ImageTransformOptions;
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Default responsive image sizes
|
|
1965
|
+
*/
|
|
1966
|
+
declare const DEFAULT_IMAGE_SIZES: ResponsiveImageSizes;
|
|
1967
|
+
/**
|
|
1968
|
+
* Build Cloudflare Image Resizing URL
|
|
1969
|
+
*
|
|
1970
|
+
* @param baseUrl - The base URL of your API (e.g., "https://api.perspect.co")
|
|
1971
|
+
* @param mediaPath - The path to the media file (e.g., "media/site/image.jpg")
|
|
1972
|
+
* @param options - Transform options
|
|
1973
|
+
* @returns Cloudflare Image Resizing URL
|
|
1974
|
+
*
|
|
1975
|
+
* @example
|
|
1976
|
+
* ```typescript
|
|
1977
|
+
* const url = buildImageUrl(
|
|
1978
|
+
* 'https://api.perspect.co',
|
|
1979
|
+
* 'media/mysite/photo.jpg',
|
|
1980
|
+
* { width: 400, format: 'webp', quality: 85 }
|
|
1981
|
+
* );
|
|
1982
|
+
* // Returns: '/cdn-cgi/image/width=400,format=webp,quality=85/https://api.perspect.co/media/mysite/photo.jpg'
|
|
1983
|
+
* ```
|
|
1984
|
+
*/
|
|
1985
|
+
declare function buildImageUrl(baseUrl: string, mediaPath: string, options?: ImageTransformOptions): string;
|
|
1986
|
+
/**
|
|
1987
|
+
* Generate responsive image URLs for different sizes
|
|
1988
|
+
*
|
|
1989
|
+
* @example
|
|
1990
|
+
* ```typescript
|
|
1991
|
+
* const urls = generateResponsiveUrls(
|
|
1992
|
+
* 'https://api.perspect.co',
|
|
1993
|
+
* 'media/mysite/photo.jpg'
|
|
1994
|
+
* );
|
|
1995
|
+
* // Returns: { thumbnail: '...', small: '...', medium: '...', large: '...', original: '...' }
|
|
1996
|
+
* ```
|
|
1997
|
+
*/
|
|
1998
|
+
declare function generateResponsiveUrls(baseUrl: string, mediaPath: string, sizes?: ResponsiveImageSizes): Record<keyof ResponsiveImageSizes, string>;
|
|
1999
|
+
/**
|
|
2000
|
+
* Generate srcset for responsive images
|
|
2001
|
+
*
|
|
2002
|
+
* @example
|
|
2003
|
+
* ```typescript
|
|
2004
|
+
* const srcset = generateSrcSet(
|
|
2005
|
+
* 'https://api.perspect.co',
|
|
2006
|
+
* 'media/mysite/photo.jpg',
|
|
2007
|
+
* [400, 800, 1200]
|
|
2008
|
+
* );
|
|
2009
|
+
* // Returns: "...?width=400 400w, ...?width=800 800w, ...?width=1200 1200w"
|
|
2010
|
+
* ```
|
|
2011
|
+
*/
|
|
2012
|
+
declare function generateSrcSet(baseUrl: string, mediaPath: string, widths?: number[]): string;
|
|
2013
|
+
/**
|
|
2014
|
+
* Generate sizes attribute for responsive images
|
|
2015
|
+
*
|
|
2016
|
+
* @example
|
|
2017
|
+
* ```typescript
|
|
2018
|
+
* const sizes = generateSizesAttribute();
|
|
2019
|
+
* // Returns: "(max-width: 640px) 100vw, (max-width: 768px) 80vw, ..."
|
|
2020
|
+
* ```
|
|
2021
|
+
*/
|
|
2022
|
+
declare function generateSizesAttribute(breakpoints?: Array<{
|
|
2023
|
+
maxWidth: string;
|
|
2024
|
+
size: string;
|
|
2025
|
+
}>): string;
|
|
2026
|
+
/**
|
|
2027
|
+
* Helper to generate complete responsive image HTML
|
|
2028
|
+
*
|
|
2029
|
+
* @example
|
|
2030
|
+
* ```typescript
|
|
2031
|
+
* const html = generateResponsiveImageHtml(
|
|
2032
|
+
* 'https://api.perspect.co',
|
|
2033
|
+
* 'media/mysite/photo.jpg',
|
|
2034
|
+
* 'My photo',
|
|
2035
|
+
* { className: 'rounded-lg', loading: 'lazy' }
|
|
2036
|
+
* );
|
|
2037
|
+
* ```
|
|
2038
|
+
*/
|
|
2039
|
+
declare function generateResponsiveImageHtml(baseUrl: string, mediaPath: string, alt: string, options?: {
|
|
2040
|
+
className?: string;
|
|
2041
|
+
loading?: 'lazy' | 'eager';
|
|
2042
|
+
decoding?: 'async' | 'sync' | 'auto';
|
|
2043
|
+
sizes?: string;
|
|
2044
|
+
widths?: number[];
|
|
2045
|
+
}): string;
|
|
2046
|
+
/**
|
|
2047
|
+
* Transform a MediaItem into responsive URLs
|
|
2048
|
+
* Convenience function for working with MediaItem objects from the API
|
|
2049
|
+
*
|
|
2050
|
+
* @example
|
|
2051
|
+
* ```typescript
|
|
2052
|
+
* import { transformMediaItem } from 'perspectapi-ts-sdk';
|
|
2053
|
+
*
|
|
2054
|
+
* const product = await client.products.getProduct('mysite', 123);
|
|
2055
|
+
* const media = product.data.media?.[0];
|
|
2056
|
+
*
|
|
2057
|
+
* if (media) {
|
|
2058
|
+
* const urls = transformMediaItem('https://api.perspect.co', media);
|
|
2059
|
+
* console.log(urls.thumbnail); // Cloudflare-transformed thumbnail URL
|
|
2060
|
+
* }
|
|
2061
|
+
* ```
|
|
2062
|
+
*/
|
|
2063
|
+
declare function transformMediaItem(baseUrl: string, media: {
|
|
2064
|
+
link: string;
|
|
2065
|
+
} | {
|
|
2066
|
+
r2_key: string;
|
|
2067
|
+
site_name: string;
|
|
2068
|
+
}, sizes?: ResponsiveImageSizes): Record<keyof ResponsiveImageSizes, string>;
|
|
2069
|
+
|
|
1928
2070
|
/**
|
|
1929
2071
|
* High-level data loading helpers that wrap the PerspectAPI SDK clients.
|
|
1930
2072
|
* These helpers provide convenient product and content loading utilities with
|
|
@@ -2057,4 +2199,4 @@ declare function createCheckoutSession(options: CheckoutSessionOptions): Promise
|
|
|
2057
2199
|
error: string;
|
|
2058
2200
|
}>;
|
|
2059
2201
|
|
|
2060
|
-
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, CategoriesClient, type Category, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, HttpClient, type HttpMethod, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductQueryParams, ProductsClient, type RequestOptions, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, type Webhook, WebhooksClient, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformProduct };
|
|
2202
|
+
export { type ApiError, type ApiKey, ApiKeysClient, type ApiResponse, AuthClient, type AuthResponse, BaseClient, type BlogPost, CategoriesClient, type Category, CheckoutClient, type CheckoutMetadata, type CheckoutMetadataValue, type CheckoutSession, type CheckoutSessionOptions, ContactClient, type ContactStatusResponse, type ContactSubmission, type ContactSubmitResponse, type Content, ContentClient, type ContentQueryParams, type ContentStatus, type ContentType, type CreateApiKeyRequest, type CreateCategoryRequest, type CreateCheckoutSessionRequest, type CreateContactRequest, type CreateContentRequest, type CreateNewsletterSubscriptionRequest, type CreateOrganizationRequest, type CreatePaymentGatewayRequest, type CreateProductRequest, type CreateSiteRequest, type CreateWebhookRequest, DEFAULT_IMAGE_SIZES, HttpClient, type HttpMethod, type ImageTransformOptions, type LoadContentBySlugOptions, type LoadContentOptions, type LoadProductBySlugOptions, type LoadProductsOptions, type LoaderLogger, type LoaderOptions, type MediaItem, NewsletterClient, type NewsletterConfirmResponse, type NewsletterList, type NewsletterPreferences, type NewsletterStatusResponse, type NewsletterSubscribeResponse, type NewsletterSubscription, type NewsletterUnsubscribeRequest, type NewsletterUnsubscribeResponse, type Organization, OrganizationsClient, type PaginatedResponse, type PaginationParams, type PaymentGateway, PerspectApiClient, type PerspectApiConfig, type Product, type ProductQueryParams, ProductsClient, type RequestOptions, type ResponsiveImageSizes, type SignInRequest, type SignUpRequest, type Site, SitesClient, type UpdateApiKeyRequest, type UpdateContentRequest, type User, type Webhook, WebhooksClient, buildImageUrl, createApiError, createCheckoutSession, createPerspectApiClient, PerspectApiClient as default, generateResponsiveImageHtml, generateResponsiveUrls, generateSizesAttribute, generateSrcSet, loadAllContent, loadContentBySlug, loadPages, loadPosts, loadProductBySlug, loadProducts, transformContent, transformMediaItem, transformProduct };
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
CheckoutClient: () => CheckoutClient,
|
|
28
28
|
ContactClient: () => ContactClient,
|
|
29
29
|
ContentClient: () => ContentClient,
|
|
30
|
+
DEFAULT_IMAGE_SIZES: () => DEFAULT_IMAGE_SIZES,
|
|
30
31
|
HttpClient: () => HttpClient,
|
|
31
32
|
NewsletterClient: () => NewsletterClient,
|
|
32
33
|
OrganizationsClient: () => OrganizationsClient,
|
|
@@ -34,10 +35,15 @@ __export(index_exports, {
|
|
|
34
35
|
ProductsClient: () => ProductsClient,
|
|
35
36
|
SitesClient: () => SitesClient,
|
|
36
37
|
WebhooksClient: () => WebhooksClient,
|
|
38
|
+
buildImageUrl: () => buildImageUrl,
|
|
37
39
|
createApiError: () => createApiError,
|
|
38
40
|
createCheckoutSession: () => createCheckoutSession,
|
|
39
41
|
createPerspectApiClient: () => createPerspectApiClient,
|
|
40
42
|
default: () => perspect_api_client_default,
|
|
43
|
+
generateResponsiveImageHtml: () => generateResponsiveImageHtml,
|
|
44
|
+
generateResponsiveUrls: () => generateResponsiveUrls,
|
|
45
|
+
generateSizesAttribute: () => generateSizesAttribute,
|
|
46
|
+
generateSrcSet: () => generateSrcSet,
|
|
41
47
|
loadAllContent: () => loadAllContent,
|
|
42
48
|
loadContentBySlug: () => loadContentBySlug,
|
|
43
49
|
loadPages: () => loadPages,
|
|
@@ -45,6 +51,7 @@ __export(index_exports, {
|
|
|
45
51
|
loadProductBySlug: () => loadProductBySlug,
|
|
46
52
|
loadProducts: () => loadProducts,
|
|
47
53
|
transformContent: () => transformContent,
|
|
54
|
+
transformMediaItem: () => transformMediaItem,
|
|
48
55
|
transformProduct: () => transformProduct
|
|
49
56
|
});
|
|
50
57
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -742,7 +749,10 @@ var ProductsClient = class extends BaseClient {
|
|
|
742
749
|
delete normalizedParams.category_id;
|
|
743
750
|
}
|
|
744
751
|
}
|
|
745
|
-
return this.http.get(
|
|
752
|
+
return this.http.get(
|
|
753
|
+
this.buildPath(this.siteScopedEndpoint(siteName, "/products", { includeSitesSegment: false })),
|
|
754
|
+
normalizedParams
|
|
755
|
+
);
|
|
746
756
|
}
|
|
747
757
|
/**
|
|
748
758
|
* Get product by ID
|
|
@@ -761,7 +771,7 @@ var ProductsClient = class extends BaseClient {
|
|
|
761
771
|
*/
|
|
762
772
|
async getProductBySlug(siteName, slug) {
|
|
763
773
|
return this.http.get(this.buildPath(
|
|
764
|
-
this.siteScopedEndpoint(siteName, `/products/slug/${encodeURIComponent(slug)}
|
|
774
|
+
this.siteScopedEndpoint(siteName, `/products/slug/${encodeURIComponent(slug)}`, { includeSitesSegment: false })
|
|
765
775
|
));
|
|
766
776
|
}
|
|
767
777
|
/**
|
|
@@ -859,7 +869,11 @@ var ProductsClient = class extends BaseClient {
|
|
|
859
869
|
search: params.search
|
|
860
870
|
} : void 0;
|
|
861
871
|
return this.http.get(this.buildPath(
|
|
862
|
-
this.siteScopedEndpoint(
|
|
872
|
+
this.siteScopedEndpoint(
|
|
873
|
+
siteName,
|
|
874
|
+
`/products/category/${encodeURIComponent(categorySlug)}`,
|
|
875
|
+
{ includeSitesSegment: false }
|
|
876
|
+
)
|
|
863
877
|
), queryParams);
|
|
864
878
|
}
|
|
865
879
|
};
|
|
@@ -1572,6 +1586,129 @@ function createPerspectApiClient(config) {
|
|
|
1572
1586
|
}
|
|
1573
1587
|
var perspect_api_client_default = PerspectApiClient;
|
|
1574
1588
|
|
|
1589
|
+
// src/utils/image-transform.ts
|
|
1590
|
+
var DEFAULT_IMAGE_SIZES = {
|
|
1591
|
+
thumbnail: {
|
|
1592
|
+
width: 150,
|
|
1593
|
+
height: 150,
|
|
1594
|
+
fit: "cover",
|
|
1595
|
+
quality: 85,
|
|
1596
|
+
format: "auto"
|
|
1597
|
+
},
|
|
1598
|
+
small: {
|
|
1599
|
+
width: 400,
|
|
1600
|
+
fit: "scale-down",
|
|
1601
|
+
quality: 85,
|
|
1602
|
+
format: "auto"
|
|
1603
|
+
},
|
|
1604
|
+
medium: {
|
|
1605
|
+
width: 800,
|
|
1606
|
+
fit: "scale-down",
|
|
1607
|
+
quality: 85,
|
|
1608
|
+
format: "auto"
|
|
1609
|
+
},
|
|
1610
|
+
large: {
|
|
1611
|
+
width: 1200,
|
|
1612
|
+
fit: "scale-down",
|
|
1613
|
+
quality: 85,
|
|
1614
|
+
format: "auto"
|
|
1615
|
+
},
|
|
1616
|
+
original: {
|
|
1617
|
+
format: "auto"
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
function buildImageUrl(baseUrl, mediaPath, options) {
|
|
1621
|
+
const sourceUrl = `${baseUrl}/${mediaPath.replace(/^\//, "")}`;
|
|
1622
|
+
if (!options || Object.keys(options).length === 0) {
|
|
1623
|
+
return sourceUrl;
|
|
1624
|
+
}
|
|
1625
|
+
const params = [];
|
|
1626
|
+
if (options.width) params.push(`width=${options.width}`);
|
|
1627
|
+
if (options.height) params.push(`height=${options.height}`);
|
|
1628
|
+
if (options.fit) params.push(`fit=${options.fit}`);
|
|
1629
|
+
if (options.gravity) params.push(`gravity=${options.gravity}`);
|
|
1630
|
+
if (options.quality) params.push(`quality=${options.quality}`);
|
|
1631
|
+
if (options.format) params.push(`format=${options.format}`);
|
|
1632
|
+
if (options.sharpen) params.push(`sharpen=${options.sharpen}`);
|
|
1633
|
+
if (options.blur) params.push(`blur=${options.blur}`);
|
|
1634
|
+
if (options.rotate) params.push(`rotate=${options.rotate}`);
|
|
1635
|
+
if (options.dpr) params.push(`dpr=${options.dpr}`);
|
|
1636
|
+
if (options.metadata) params.push(`metadata=${options.metadata}`);
|
|
1637
|
+
if (options.background) params.push(`background=${options.background}`);
|
|
1638
|
+
if (options.trim) {
|
|
1639
|
+
const trimParts = [];
|
|
1640
|
+
if (options.trim.top) trimParts.push(`${options.trim.top}`);
|
|
1641
|
+
if (options.trim.right) trimParts.push(`${options.trim.right}`);
|
|
1642
|
+
if (options.trim.bottom) trimParts.push(`${options.trim.bottom}`);
|
|
1643
|
+
if (options.trim.left) trimParts.push(`${options.trim.left}`);
|
|
1644
|
+
if (trimParts.length > 0) {
|
|
1645
|
+
params.push(`trim=${trimParts.join(";")}`);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
const queryString = params.join(",");
|
|
1649
|
+
return `/cdn-cgi/image/${queryString}/${sourceUrl}`;
|
|
1650
|
+
}
|
|
1651
|
+
function generateResponsiveUrls(baseUrl, mediaPath, sizes = DEFAULT_IMAGE_SIZES) {
|
|
1652
|
+
return {
|
|
1653
|
+
thumbnail: buildImageUrl(baseUrl, mediaPath, sizes.thumbnail),
|
|
1654
|
+
small: buildImageUrl(baseUrl, mediaPath, sizes.small),
|
|
1655
|
+
medium: buildImageUrl(baseUrl, mediaPath, sizes.medium),
|
|
1656
|
+
large: buildImageUrl(baseUrl, mediaPath, sizes.large),
|
|
1657
|
+
original: buildImageUrl(baseUrl, mediaPath, sizes.original)
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
function generateSrcSet(baseUrl, mediaPath, widths = [400, 800, 1200, 1600]) {
|
|
1661
|
+
return widths.map((width) => {
|
|
1662
|
+
const url = buildImageUrl(baseUrl, mediaPath, {
|
|
1663
|
+
width,
|
|
1664
|
+
fit: "scale-down",
|
|
1665
|
+
quality: 85,
|
|
1666
|
+
format: "auto"
|
|
1667
|
+
});
|
|
1668
|
+
return `${url} ${width}w`;
|
|
1669
|
+
}).join(", ");
|
|
1670
|
+
}
|
|
1671
|
+
function generateSizesAttribute(breakpoints = [
|
|
1672
|
+
{ maxWidth: "640px", size: "100vw" },
|
|
1673
|
+
{ maxWidth: "768px", size: "80vw" },
|
|
1674
|
+
{ maxWidth: "1024px", size: "60vw" },
|
|
1675
|
+
{ maxWidth: "1280px", size: "50vw" }
|
|
1676
|
+
]) {
|
|
1677
|
+
const mediaQueries = breakpoints.map(
|
|
1678
|
+
(bp) => `(max-width: ${bp.maxWidth}) ${bp.size}`
|
|
1679
|
+
);
|
|
1680
|
+
mediaQueries.push("40vw");
|
|
1681
|
+
return mediaQueries.join(", ");
|
|
1682
|
+
}
|
|
1683
|
+
function generateResponsiveImageHtml(baseUrl, mediaPath, alt, options) {
|
|
1684
|
+
const srcset = generateSrcSet(baseUrl, mediaPath, options?.widths);
|
|
1685
|
+
const sizes = options?.sizes || generateSizesAttribute();
|
|
1686
|
+
const src = buildImageUrl(baseUrl, mediaPath, {
|
|
1687
|
+
width: 800,
|
|
1688
|
+
fit: "scale-down",
|
|
1689
|
+
quality: 85,
|
|
1690
|
+
format: "auto"
|
|
1691
|
+
});
|
|
1692
|
+
return `<img
|
|
1693
|
+
src="${src}"
|
|
1694
|
+
srcset="${srcset}"
|
|
1695
|
+
sizes="${sizes}"
|
|
1696
|
+
alt="${alt}"
|
|
1697
|
+
${options?.className ? `class="${options.className}"` : ""}
|
|
1698
|
+
${options?.loading ? `loading="${options.loading}"` : 'loading="lazy"'}
|
|
1699
|
+
${options?.decoding ? `decoding="${options.decoding}"` : 'decoding="async"'}
|
|
1700
|
+
/>`;
|
|
1701
|
+
}
|
|
1702
|
+
function transformMediaItem(baseUrl, media, sizes) {
|
|
1703
|
+
let mediaPath;
|
|
1704
|
+
if ("link" in media) {
|
|
1705
|
+
mediaPath = media.link.replace(/^https?:\/\/[^/]+\//, "");
|
|
1706
|
+
} else {
|
|
1707
|
+
mediaPath = `media/${media.site_name}/${media.r2_key.split("/").pop()}`;
|
|
1708
|
+
}
|
|
1709
|
+
return generateResponsiveUrls(baseUrl, mediaPath, sizes);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1575
1712
|
// src/loaders.ts
|
|
1576
1713
|
var noopLogger = {};
|
|
1577
1714
|
var log = (logger, level, ...args) => {
|
|
@@ -1968,6 +2105,7 @@ async function createCheckoutSession(options) {
|
|
|
1968
2105
|
CheckoutClient,
|
|
1969
2106
|
ContactClient,
|
|
1970
2107
|
ContentClient,
|
|
2108
|
+
DEFAULT_IMAGE_SIZES,
|
|
1971
2109
|
HttpClient,
|
|
1972
2110
|
NewsletterClient,
|
|
1973
2111
|
OrganizationsClient,
|
|
@@ -1975,9 +2113,14 @@ async function createCheckoutSession(options) {
|
|
|
1975
2113
|
ProductsClient,
|
|
1976
2114
|
SitesClient,
|
|
1977
2115
|
WebhooksClient,
|
|
2116
|
+
buildImageUrl,
|
|
1978
2117
|
createApiError,
|
|
1979
2118
|
createCheckoutSession,
|
|
1980
2119
|
createPerspectApiClient,
|
|
2120
|
+
generateResponsiveImageHtml,
|
|
2121
|
+
generateResponsiveUrls,
|
|
2122
|
+
generateSizesAttribute,
|
|
2123
|
+
generateSrcSet,
|
|
1981
2124
|
loadAllContent,
|
|
1982
2125
|
loadContentBySlug,
|
|
1983
2126
|
loadPages,
|
|
@@ -1985,5 +2128,6 @@ async function createCheckoutSession(options) {
|
|
|
1985
2128
|
loadProductBySlug,
|
|
1986
2129
|
loadProducts,
|
|
1987
2130
|
transformContent,
|
|
2131
|
+
transformMediaItem,
|
|
1988
2132
|
transformProduct
|
|
1989
2133
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -691,7 +691,10 @@ var ProductsClient = class extends BaseClient {
|
|
|
691
691
|
delete normalizedParams.category_id;
|
|
692
692
|
}
|
|
693
693
|
}
|
|
694
|
-
return this.http.get(
|
|
694
|
+
return this.http.get(
|
|
695
|
+
this.buildPath(this.siteScopedEndpoint(siteName, "/products", { includeSitesSegment: false })),
|
|
696
|
+
normalizedParams
|
|
697
|
+
);
|
|
695
698
|
}
|
|
696
699
|
/**
|
|
697
700
|
* Get product by ID
|
|
@@ -710,7 +713,7 @@ var ProductsClient = class extends BaseClient {
|
|
|
710
713
|
*/
|
|
711
714
|
async getProductBySlug(siteName, slug) {
|
|
712
715
|
return this.http.get(this.buildPath(
|
|
713
|
-
this.siteScopedEndpoint(siteName, `/products/slug/${encodeURIComponent(slug)}
|
|
716
|
+
this.siteScopedEndpoint(siteName, `/products/slug/${encodeURIComponent(slug)}`, { includeSitesSegment: false })
|
|
714
717
|
));
|
|
715
718
|
}
|
|
716
719
|
/**
|
|
@@ -808,7 +811,11 @@ var ProductsClient = class extends BaseClient {
|
|
|
808
811
|
search: params.search
|
|
809
812
|
} : void 0;
|
|
810
813
|
return this.http.get(this.buildPath(
|
|
811
|
-
this.siteScopedEndpoint(
|
|
814
|
+
this.siteScopedEndpoint(
|
|
815
|
+
siteName,
|
|
816
|
+
`/products/category/${encodeURIComponent(categorySlug)}`,
|
|
817
|
+
{ includeSitesSegment: false }
|
|
818
|
+
)
|
|
812
819
|
), queryParams);
|
|
813
820
|
}
|
|
814
821
|
};
|
|
@@ -1521,6 +1528,129 @@ function createPerspectApiClient(config) {
|
|
|
1521
1528
|
}
|
|
1522
1529
|
var perspect_api_client_default = PerspectApiClient;
|
|
1523
1530
|
|
|
1531
|
+
// src/utils/image-transform.ts
|
|
1532
|
+
var DEFAULT_IMAGE_SIZES = {
|
|
1533
|
+
thumbnail: {
|
|
1534
|
+
width: 150,
|
|
1535
|
+
height: 150,
|
|
1536
|
+
fit: "cover",
|
|
1537
|
+
quality: 85,
|
|
1538
|
+
format: "auto"
|
|
1539
|
+
},
|
|
1540
|
+
small: {
|
|
1541
|
+
width: 400,
|
|
1542
|
+
fit: "scale-down",
|
|
1543
|
+
quality: 85,
|
|
1544
|
+
format: "auto"
|
|
1545
|
+
},
|
|
1546
|
+
medium: {
|
|
1547
|
+
width: 800,
|
|
1548
|
+
fit: "scale-down",
|
|
1549
|
+
quality: 85,
|
|
1550
|
+
format: "auto"
|
|
1551
|
+
},
|
|
1552
|
+
large: {
|
|
1553
|
+
width: 1200,
|
|
1554
|
+
fit: "scale-down",
|
|
1555
|
+
quality: 85,
|
|
1556
|
+
format: "auto"
|
|
1557
|
+
},
|
|
1558
|
+
original: {
|
|
1559
|
+
format: "auto"
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
function buildImageUrl(baseUrl, mediaPath, options) {
|
|
1563
|
+
const sourceUrl = `${baseUrl}/${mediaPath.replace(/^\//, "")}`;
|
|
1564
|
+
if (!options || Object.keys(options).length === 0) {
|
|
1565
|
+
return sourceUrl;
|
|
1566
|
+
}
|
|
1567
|
+
const params = [];
|
|
1568
|
+
if (options.width) params.push(`width=${options.width}`);
|
|
1569
|
+
if (options.height) params.push(`height=${options.height}`);
|
|
1570
|
+
if (options.fit) params.push(`fit=${options.fit}`);
|
|
1571
|
+
if (options.gravity) params.push(`gravity=${options.gravity}`);
|
|
1572
|
+
if (options.quality) params.push(`quality=${options.quality}`);
|
|
1573
|
+
if (options.format) params.push(`format=${options.format}`);
|
|
1574
|
+
if (options.sharpen) params.push(`sharpen=${options.sharpen}`);
|
|
1575
|
+
if (options.blur) params.push(`blur=${options.blur}`);
|
|
1576
|
+
if (options.rotate) params.push(`rotate=${options.rotate}`);
|
|
1577
|
+
if (options.dpr) params.push(`dpr=${options.dpr}`);
|
|
1578
|
+
if (options.metadata) params.push(`metadata=${options.metadata}`);
|
|
1579
|
+
if (options.background) params.push(`background=${options.background}`);
|
|
1580
|
+
if (options.trim) {
|
|
1581
|
+
const trimParts = [];
|
|
1582
|
+
if (options.trim.top) trimParts.push(`${options.trim.top}`);
|
|
1583
|
+
if (options.trim.right) trimParts.push(`${options.trim.right}`);
|
|
1584
|
+
if (options.trim.bottom) trimParts.push(`${options.trim.bottom}`);
|
|
1585
|
+
if (options.trim.left) trimParts.push(`${options.trim.left}`);
|
|
1586
|
+
if (trimParts.length > 0) {
|
|
1587
|
+
params.push(`trim=${trimParts.join(";")}`);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
const queryString = params.join(",");
|
|
1591
|
+
return `/cdn-cgi/image/${queryString}/${sourceUrl}`;
|
|
1592
|
+
}
|
|
1593
|
+
function generateResponsiveUrls(baseUrl, mediaPath, sizes = DEFAULT_IMAGE_SIZES) {
|
|
1594
|
+
return {
|
|
1595
|
+
thumbnail: buildImageUrl(baseUrl, mediaPath, sizes.thumbnail),
|
|
1596
|
+
small: buildImageUrl(baseUrl, mediaPath, sizes.small),
|
|
1597
|
+
medium: buildImageUrl(baseUrl, mediaPath, sizes.medium),
|
|
1598
|
+
large: buildImageUrl(baseUrl, mediaPath, sizes.large),
|
|
1599
|
+
original: buildImageUrl(baseUrl, mediaPath, sizes.original)
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
function generateSrcSet(baseUrl, mediaPath, widths = [400, 800, 1200, 1600]) {
|
|
1603
|
+
return widths.map((width) => {
|
|
1604
|
+
const url = buildImageUrl(baseUrl, mediaPath, {
|
|
1605
|
+
width,
|
|
1606
|
+
fit: "scale-down",
|
|
1607
|
+
quality: 85,
|
|
1608
|
+
format: "auto"
|
|
1609
|
+
});
|
|
1610
|
+
return `${url} ${width}w`;
|
|
1611
|
+
}).join(", ");
|
|
1612
|
+
}
|
|
1613
|
+
function generateSizesAttribute(breakpoints = [
|
|
1614
|
+
{ maxWidth: "640px", size: "100vw" },
|
|
1615
|
+
{ maxWidth: "768px", size: "80vw" },
|
|
1616
|
+
{ maxWidth: "1024px", size: "60vw" },
|
|
1617
|
+
{ maxWidth: "1280px", size: "50vw" }
|
|
1618
|
+
]) {
|
|
1619
|
+
const mediaQueries = breakpoints.map(
|
|
1620
|
+
(bp) => `(max-width: ${bp.maxWidth}) ${bp.size}`
|
|
1621
|
+
);
|
|
1622
|
+
mediaQueries.push("40vw");
|
|
1623
|
+
return mediaQueries.join(", ");
|
|
1624
|
+
}
|
|
1625
|
+
function generateResponsiveImageHtml(baseUrl, mediaPath, alt, options) {
|
|
1626
|
+
const srcset = generateSrcSet(baseUrl, mediaPath, options?.widths);
|
|
1627
|
+
const sizes = options?.sizes || generateSizesAttribute();
|
|
1628
|
+
const src = buildImageUrl(baseUrl, mediaPath, {
|
|
1629
|
+
width: 800,
|
|
1630
|
+
fit: "scale-down",
|
|
1631
|
+
quality: 85,
|
|
1632
|
+
format: "auto"
|
|
1633
|
+
});
|
|
1634
|
+
return `<img
|
|
1635
|
+
src="${src}"
|
|
1636
|
+
srcset="${srcset}"
|
|
1637
|
+
sizes="${sizes}"
|
|
1638
|
+
alt="${alt}"
|
|
1639
|
+
${options?.className ? `class="${options.className}"` : ""}
|
|
1640
|
+
${options?.loading ? `loading="${options.loading}"` : 'loading="lazy"'}
|
|
1641
|
+
${options?.decoding ? `decoding="${options.decoding}"` : 'decoding="async"'}
|
|
1642
|
+
/>`;
|
|
1643
|
+
}
|
|
1644
|
+
function transformMediaItem(baseUrl, media, sizes) {
|
|
1645
|
+
let mediaPath;
|
|
1646
|
+
if ("link" in media) {
|
|
1647
|
+
mediaPath = media.link.replace(/^https?:\/\/[^/]+\//, "");
|
|
1648
|
+
} else {
|
|
1649
|
+
mediaPath = `media/${media.site_name}/${media.r2_key.split("/").pop()}`;
|
|
1650
|
+
}
|
|
1651
|
+
return generateResponsiveUrls(baseUrl, mediaPath, sizes);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1524
1654
|
// src/loaders.ts
|
|
1525
1655
|
var noopLogger = {};
|
|
1526
1656
|
var log = (logger, level, ...args) => {
|
|
@@ -1916,6 +2046,7 @@ export {
|
|
|
1916
2046
|
CheckoutClient,
|
|
1917
2047
|
ContactClient,
|
|
1918
2048
|
ContentClient,
|
|
2049
|
+
DEFAULT_IMAGE_SIZES,
|
|
1919
2050
|
HttpClient,
|
|
1920
2051
|
NewsletterClient,
|
|
1921
2052
|
OrganizationsClient,
|
|
@@ -1923,10 +2054,15 @@ export {
|
|
|
1923
2054
|
ProductsClient,
|
|
1924
2055
|
SitesClient,
|
|
1925
2056
|
WebhooksClient,
|
|
2057
|
+
buildImageUrl,
|
|
1926
2058
|
createApiError,
|
|
1927
2059
|
createCheckoutSession,
|
|
1928
2060
|
createPerspectApiClient,
|
|
1929
2061
|
perspect_api_client_default as default,
|
|
2062
|
+
generateResponsiveImageHtml,
|
|
2063
|
+
generateResponsiveUrls,
|
|
2064
|
+
generateSizesAttribute,
|
|
2065
|
+
generateSrcSet,
|
|
1930
2066
|
loadAllContent,
|
|
1931
2067
|
loadContentBySlug,
|
|
1932
2068
|
loadPages,
|
|
@@ -1934,5 +2070,6 @@ export {
|
|
|
1934
2070
|
loadProductBySlug,
|
|
1935
2071
|
loadProducts,
|
|
1936
2072
|
transformContent,
|
|
2073
|
+
transformMediaItem,
|
|
1937
2074
|
transformProduct
|
|
1938
2075
|
};
|
package/package.json
CHANGED
|
@@ -50,7 +50,10 @@ export class ProductsClient extends BaseClient {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
return this.http.get(
|
|
53
|
+
return this.http.get(
|
|
54
|
+
this.buildPath(this.siteScopedEndpoint(siteName, '/products', { includeSitesSegment: false })),
|
|
55
|
+
normalizedParams
|
|
56
|
+
);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
/**
|
|
@@ -72,7 +75,7 @@ export class ProductsClient extends BaseClient {
|
|
|
72
75
|
*/
|
|
73
76
|
async getProductBySlug(siteName: string, slug: string): Promise<ApiResponse<Product & { variants?: any[] }>> {
|
|
74
77
|
return this.http.get(this.buildPath(
|
|
75
|
-
this.siteScopedEndpoint(siteName, `/products/slug/${encodeURIComponent(slug)}
|
|
78
|
+
this.siteScopedEndpoint(siteName, `/products/slug/${encodeURIComponent(slug)}`, { includeSitesSegment: false })
|
|
76
79
|
));
|
|
77
80
|
}
|
|
78
81
|
|
|
@@ -241,7 +244,11 @@ export class ProductsClient extends BaseClient {
|
|
|
241
244
|
} : undefined;
|
|
242
245
|
|
|
243
246
|
return this.http.get(this.buildPath(
|
|
244
|
-
this.siteScopedEndpoint(
|
|
247
|
+
this.siteScopedEndpoint(
|
|
248
|
+
siteName,
|
|
249
|
+
`/products/category/${encodeURIComponent(categorySlug)}`,
|
|
250
|
+
{ includeSitesSegment: false }
|
|
251
|
+
)
|
|
245
252
|
), queryParams);
|
|
246
253
|
}
|
|
247
254
|
}
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,22 @@ export { BaseClient } from './client/base-client';
|
|
|
26
26
|
// Utilities
|
|
27
27
|
export { HttpClient, createApiError } from './utils/http-client';
|
|
28
28
|
|
|
29
|
+
// Image transformation utilities
|
|
30
|
+
export {
|
|
31
|
+
buildImageUrl,
|
|
32
|
+
generateResponsiveUrls,
|
|
33
|
+
generateSrcSet,
|
|
34
|
+
generateSizesAttribute,
|
|
35
|
+
generateResponsiveImageHtml,
|
|
36
|
+
transformMediaItem,
|
|
37
|
+
DEFAULT_IMAGE_SIZES
|
|
38
|
+
} from './utils/image-transform';
|
|
39
|
+
|
|
40
|
+
export type {
|
|
41
|
+
ImageTransformOptions,
|
|
42
|
+
ResponsiveImageSizes
|
|
43
|
+
} from './utils/image-transform';
|
|
44
|
+
|
|
29
45
|
// High-level data loaders
|
|
30
46
|
export {
|
|
31
47
|
loadProducts,
|
package/src/types/index.ts
CHANGED
|
@@ -221,6 +221,7 @@ export interface Content {
|
|
|
221
221
|
contentMarkdown?: string;
|
|
222
222
|
custom?: Record<string, any>;
|
|
223
223
|
slug?: string;
|
|
224
|
+
slug_prefix?: string;
|
|
224
225
|
pageStatus: ContentStatus;
|
|
225
226
|
pageType: ContentType;
|
|
226
227
|
userId?: number;
|
|
@@ -246,6 +247,7 @@ export interface ContentQueryParams extends PaginationParams {
|
|
|
246
247
|
page_type?: ContentType;
|
|
247
248
|
search?: string;
|
|
248
249
|
user_id?: number;
|
|
250
|
+
slug_prefix?: string;
|
|
249
251
|
}
|
|
250
252
|
|
|
251
253
|
// Categories
|
|
@@ -320,6 +322,7 @@ export interface ProductQueryParams extends PaginationParams {
|
|
|
320
322
|
search?: string;
|
|
321
323
|
category?: string | string[];
|
|
322
324
|
category_id?: number | string | Array<number | string>;
|
|
325
|
+
slug_prefix?: string;
|
|
323
326
|
}
|
|
324
327
|
|
|
325
328
|
export interface BlogPost {
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Image Resizing Integration
|
|
3
|
+
* Transforms images on-the-fly using Cloudflare's Image Resizing service
|
|
4
|
+
* https://developers.cloudflare.com/images/transform-images/transform-via-url/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface ImageTransformOptions {
|
|
8
|
+
width?: number;
|
|
9
|
+
height?: number;
|
|
10
|
+
fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
|
|
11
|
+
gravity?: 'auto' | 'left' | 'right' | 'top' | 'bottom' | 'center';
|
|
12
|
+
quality?: number; // 1-100
|
|
13
|
+
format?: 'auto' | 'avif' | 'webp' | 'json' | 'jpeg' | 'png';
|
|
14
|
+
sharpen?: number; // 0-10
|
|
15
|
+
blur?: number; // 0-250
|
|
16
|
+
rotate?: 0 | 90 | 180 | 270;
|
|
17
|
+
dpr?: number; // Device Pixel Ratio: 1, 2, or 3
|
|
18
|
+
metadata?: 'keep' | 'copyright' | 'none';
|
|
19
|
+
background?: string; // hex color for padding
|
|
20
|
+
trim?: {
|
|
21
|
+
top?: number;
|
|
22
|
+
right?: number;
|
|
23
|
+
bottom?: number;
|
|
24
|
+
left?: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ResponsiveImageSizes {
|
|
29
|
+
thumbnail: ImageTransformOptions;
|
|
30
|
+
small: ImageTransformOptions;
|
|
31
|
+
medium: ImageTransformOptions;
|
|
32
|
+
large: ImageTransformOptions;
|
|
33
|
+
original: ImageTransformOptions;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Default responsive image sizes
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_IMAGE_SIZES: ResponsiveImageSizes = {
|
|
40
|
+
thumbnail: {
|
|
41
|
+
width: 150,
|
|
42
|
+
height: 150,
|
|
43
|
+
fit: 'cover',
|
|
44
|
+
quality: 85,
|
|
45
|
+
format: 'auto'
|
|
46
|
+
},
|
|
47
|
+
small: {
|
|
48
|
+
width: 400,
|
|
49
|
+
fit: 'scale-down',
|
|
50
|
+
quality: 85,
|
|
51
|
+
format: 'auto'
|
|
52
|
+
},
|
|
53
|
+
medium: {
|
|
54
|
+
width: 800,
|
|
55
|
+
fit: 'scale-down',
|
|
56
|
+
quality: 85,
|
|
57
|
+
format: 'auto'
|
|
58
|
+
},
|
|
59
|
+
large: {
|
|
60
|
+
width: 1200,
|
|
61
|
+
fit: 'scale-down',
|
|
62
|
+
quality: 85,
|
|
63
|
+
format: 'auto'
|
|
64
|
+
},
|
|
65
|
+
original: {
|
|
66
|
+
format: 'auto'
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Build Cloudflare Image Resizing URL
|
|
72
|
+
*
|
|
73
|
+
* @param baseUrl - The base URL of your API (e.g., "https://api.perspect.co")
|
|
74
|
+
* @param mediaPath - The path to the media file (e.g., "media/site/image.jpg")
|
|
75
|
+
* @param options - Transform options
|
|
76
|
+
* @returns Cloudflare Image Resizing URL
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const url = buildImageUrl(
|
|
81
|
+
* 'https://api.perspect.co',
|
|
82
|
+
* 'media/mysite/photo.jpg',
|
|
83
|
+
* { width: 400, format: 'webp', quality: 85 }
|
|
84
|
+
* );
|
|
85
|
+
* // Returns: '/cdn-cgi/image/width=400,format=webp,quality=85/https://api.perspect.co/media/mysite/photo.jpg'
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function buildImageUrl(
|
|
89
|
+
baseUrl: string,
|
|
90
|
+
mediaPath: string,
|
|
91
|
+
options?: ImageTransformOptions
|
|
92
|
+
): string {
|
|
93
|
+
// Construct the full source URL
|
|
94
|
+
const sourceUrl = `${baseUrl}/${mediaPath.replace(/^\//, '')}`;
|
|
95
|
+
|
|
96
|
+
if (!options || Object.keys(options).length === 0) {
|
|
97
|
+
// Return original image URL
|
|
98
|
+
return sourceUrl;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Build transform options string
|
|
102
|
+
const params: string[] = [];
|
|
103
|
+
|
|
104
|
+
if (options.width) params.push(`width=${options.width}`);
|
|
105
|
+
if (options.height) params.push(`height=${options.height}`);
|
|
106
|
+
if (options.fit) params.push(`fit=${options.fit}`);
|
|
107
|
+
if (options.gravity) params.push(`gravity=${options.gravity}`);
|
|
108
|
+
if (options.quality) params.push(`quality=${options.quality}`);
|
|
109
|
+
if (options.format) params.push(`format=${options.format}`);
|
|
110
|
+
if (options.sharpen) params.push(`sharpen=${options.sharpen}`);
|
|
111
|
+
if (options.blur) params.push(`blur=${options.blur}`);
|
|
112
|
+
if (options.rotate) params.push(`rotate=${options.rotate}`);
|
|
113
|
+
if (options.dpr) params.push(`dpr=${options.dpr}`);
|
|
114
|
+
if (options.metadata) params.push(`metadata=${options.metadata}`);
|
|
115
|
+
if (options.background) params.push(`background=${options.background}`);
|
|
116
|
+
|
|
117
|
+
if (options.trim) {
|
|
118
|
+
const trimParts: string[] = [];
|
|
119
|
+
if (options.trim.top) trimParts.push(`${options.trim.top}`);
|
|
120
|
+
if (options.trim.right) trimParts.push(`${options.trim.right}`);
|
|
121
|
+
if (options.trim.bottom) trimParts.push(`${options.trim.bottom}`);
|
|
122
|
+
if (options.trim.left) trimParts.push(`${options.trim.left}`);
|
|
123
|
+
if (trimParts.length > 0) {
|
|
124
|
+
params.push(`trim=${trimParts.join(';')}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const queryString = params.join(',');
|
|
129
|
+
|
|
130
|
+
// Cloudflare Image Resizing URL format:
|
|
131
|
+
// /cdn-cgi/image/{options}/{source-image-url}
|
|
132
|
+
// Note: This only works on Cloudflare zones with Image Resizing enabled
|
|
133
|
+
return `/cdn-cgi/image/${queryString}/${sourceUrl}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generate responsive image URLs for different sizes
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const urls = generateResponsiveUrls(
|
|
142
|
+
* 'https://api.perspect.co',
|
|
143
|
+
* 'media/mysite/photo.jpg'
|
|
144
|
+
* );
|
|
145
|
+
* // Returns: { thumbnail: '...', small: '...', medium: '...', large: '...', original: '...' }
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export function generateResponsiveUrls(
|
|
149
|
+
baseUrl: string,
|
|
150
|
+
mediaPath: string,
|
|
151
|
+
sizes: ResponsiveImageSizes = DEFAULT_IMAGE_SIZES
|
|
152
|
+
): Record<keyof ResponsiveImageSizes, string> {
|
|
153
|
+
return {
|
|
154
|
+
thumbnail: buildImageUrl(baseUrl, mediaPath, sizes.thumbnail),
|
|
155
|
+
small: buildImageUrl(baseUrl, mediaPath, sizes.small),
|
|
156
|
+
medium: buildImageUrl(baseUrl, mediaPath, sizes.medium),
|
|
157
|
+
large: buildImageUrl(baseUrl, mediaPath, sizes.large),
|
|
158
|
+
original: buildImageUrl(baseUrl, mediaPath, sizes.original)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Generate srcset for responsive images
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* const srcset = generateSrcSet(
|
|
168
|
+
* 'https://api.perspect.co',
|
|
169
|
+
* 'media/mysite/photo.jpg',
|
|
170
|
+
* [400, 800, 1200]
|
|
171
|
+
* );
|
|
172
|
+
* // Returns: "...?width=400 400w, ...?width=800 800w, ...?width=1200 1200w"
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
export function generateSrcSet(
|
|
176
|
+
baseUrl: string,
|
|
177
|
+
mediaPath: string,
|
|
178
|
+
widths: number[] = [400, 800, 1200, 1600]
|
|
179
|
+
): string {
|
|
180
|
+
return widths
|
|
181
|
+
.map((width) => {
|
|
182
|
+
const url = buildImageUrl(baseUrl, mediaPath, {
|
|
183
|
+
width,
|
|
184
|
+
fit: 'scale-down',
|
|
185
|
+
quality: 85,
|
|
186
|
+
format: 'auto'
|
|
187
|
+
});
|
|
188
|
+
return `${url} ${width}w`;
|
|
189
|
+
})
|
|
190
|
+
.join(', ');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Generate sizes attribute for responsive images
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* const sizes = generateSizesAttribute();
|
|
199
|
+
* // Returns: "(max-width: 640px) 100vw, (max-width: 768px) 80vw, ..."
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
export function generateSizesAttribute(
|
|
203
|
+
breakpoints: Array<{ maxWidth: string; size: string }> = [
|
|
204
|
+
{ maxWidth: '640px', size: '100vw' },
|
|
205
|
+
{ maxWidth: '768px', size: '80vw' },
|
|
206
|
+
{ maxWidth: '1024px', size: '60vw' },
|
|
207
|
+
{ maxWidth: '1280px', size: '50vw' }
|
|
208
|
+
]
|
|
209
|
+
): string {
|
|
210
|
+
const mediaQueries = breakpoints.map(
|
|
211
|
+
(bp) => `(max-width: ${bp.maxWidth}) ${bp.size}`
|
|
212
|
+
);
|
|
213
|
+
mediaQueries.push('40vw'); // default size
|
|
214
|
+
return mediaQueries.join(', ');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Helper to generate complete responsive image HTML
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```typescript
|
|
222
|
+
* const html = generateResponsiveImageHtml(
|
|
223
|
+
* 'https://api.perspect.co',
|
|
224
|
+
* 'media/mysite/photo.jpg',
|
|
225
|
+
* 'My photo',
|
|
226
|
+
* { className: 'rounded-lg', loading: 'lazy' }
|
|
227
|
+
* );
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
export function generateResponsiveImageHtml(
|
|
231
|
+
baseUrl: string,
|
|
232
|
+
mediaPath: string,
|
|
233
|
+
alt: string,
|
|
234
|
+
options?: {
|
|
235
|
+
className?: string;
|
|
236
|
+
loading?: 'lazy' | 'eager';
|
|
237
|
+
decoding?: 'async' | 'sync' | 'auto';
|
|
238
|
+
sizes?: string;
|
|
239
|
+
widths?: number[];
|
|
240
|
+
}
|
|
241
|
+
): string {
|
|
242
|
+
const srcset = generateSrcSet(baseUrl, mediaPath, options?.widths);
|
|
243
|
+
const sizes = options?.sizes || generateSizesAttribute();
|
|
244
|
+
const src = buildImageUrl(baseUrl, mediaPath, {
|
|
245
|
+
width: 800,
|
|
246
|
+
fit: 'scale-down',
|
|
247
|
+
quality: 85,
|
|
248
|
+
format: 'auto'
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return `<img
|
|
252
|
+
src="${src}"
|
|
253
|
+
srcset="${srcset}"
|
|
254
|
+
sizes="${sizes}"
|
|
255
|
+
alt="${alt}"
|
|
256
|
+
${options?.className ? `class="${options.className}"` : ''}
|
|
257
|
+
${options?.loading ? `loading="${options.loading}"` : 'loading="lazy"'}
|
|
258
|
+
${options?.decoding ? `decoding="${options.decoding}"` : 'decoding="async"'}
|
|
259
|
+
/>`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Transform a MediaItem into responsive URLs
|
|
264
|
+
* Convenience function for working with MediaItem objects from the API
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* import { transformMediaItem } from 'perspectapi-ts-sdk';
|
|
269
|
+
*
|
|
270
|
+
* const product = await client.products.getProduct('mysite', 123);
|
|
271
|
+
* const media = product.data.media?.[0];
|
|
272
|
+
*
|
|
273
|
+
* if (media) {
|
|
274
|
+
* const urls = transformMediaItem('https://api.perspect.co', media);
|
|
275
|
+
* console.log(urls.thumbnail); // Cloudflare-transformed thumbnail URL
|
|
276
|
+
* }
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export function transformMediaItem(
|
|
280
|
+
baseUrl: string,
|
|
281
|
+
media: { link: string } | { r2_key: string; site_name: string },
|
|
282
|
+
sizes?: ResponsiveImageSizes
|
|
283
|
+
): Record<keyof ResponsiveImageSizes, string> {
|
|
284
|
+
// Determine the media path
|
|
285
|
+
let mediaPath: string;
|
|
286
|
+
|
|
287
|
+
if ('link' in media) {
|
|
288
|
+
// Extract path from full URL
|
|
289
|
+
mediaPath = media.link.replace(/^https?:\/\/[^/]+\//, '');
|
|
290
|
+
} else {
|
|
291
|
+
// Construct from r2_key
|
|
292
|
+
mediaPath = `media/${media.site_name}/${media.r2_key.split('/').pop()}`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return generateResponsiveUrls(baseUrl, mediaPath, sizes);
|
|
296
|
+
}
|