@yatoday/astro-ui 0.0.2
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/LICENSE +21 -0
- package/README.md +2 -0
- package/astro.d.ts +31 -0
- package/astro.js +19 -0
- package/components/Analytics/Analytics.astro +15 -0
- package/components/Analytics/AnalyticsGoogle.astro +17 -0
- package/components/Analytics/types.ts +6 -0
- package/components/Background/Background.astro +9 -0
- package/components/Background/Background.svelte +13 -0
- package/components/Background/types.ts +9 -0
- package/components/Card0/Card0.astro +23 -0
- package/components/Card0/card0.ts +6 -0
- package/components/Card0/types.ts +6 -0
- package/components/ConditionalWrapper/ConditionalWrapper.astro +15 -0
- package/components/ConditionalWrapper/ConditionalWrapper.svelte +18 -0
- package/components/ConditionalWrapper/types.ts +10 -0
- package/components/DarkMode/DarkMode.astro +77 -0
- package/components/DarkMode/types.ts +7 -0
- package/components/Layout/Layout.astro +35 -0
- package/components/Layout/types.ts +13 -0
- package/components/Metadata/AstroSeo.astro +40 -0
- package/components/Metadata/Metadata.astro +60 -0
- package/components/Metadata/types.ts +186 -0
- package/components/Metadata/utils/buildTags.ts +380 -0
- package/components/SeoAnalytics/SeoAnalytics.astro +15 -0
- package/components/SeoAnalytics/types.ts +3 -0
- package/components/SeoAnalyticsGoogleProvider/SeoAnalyticsGoogleProvider.astro +17 -0
- package/components/SeoAnalyticsGoogleProvider/types.ts +4 -0
- package/components/SeoAstroSeo/SeoAstroSeo.astro +40 -0
- package/components/SeoAstroSeo/SeoAstroSeo.svelte +3 -0
- package/components/SeoAstroSeo/types.ts +146 -0
- package/components/SeoMetadata/SeoMetadata.astro +8 -0
- package/components/SeoMetadata/types.ts +38 -0
- package/components/SeoSiteVerification/SeoSiteVerification.astro +6 -0
- package/components/SeoSiteVerification/types.ts +3 -0
- package/components/SiteVerification/SiteVerification.astro +6 -0
- package/components/SiteVerification/types.ts +1 -0
- package/components/WidgetWrapper/WidgetWrapper.astro +31 -0
- package/components/WidgetWrapper/WidgetWrapper.ts +6 -0
- package/components/WidgetWrapper/types.ts +10 -0
- package/index.d.ts +180 -0
- package/index.js +6 -0
- package/package.json +99 -0
- package/svelte.d.ts +32 -0
- package/svelte.js +19 -0
- package/utils/buildTags.ts +380 -0
- package/utils/css.ts +39 -0
- package/utils/images-optimization.ts +356 -0
- package/utils/images.ts +138 -0
- package/utils/index.ts +5 -0
- package/utils/permalinks.ts +38 -0
- package/utils/utils.ts +58 -0
- package/vendor-config/index.ts +115 -0
- package/vendor-config/types.d.ts +10 -0
- package/vendor-config/utils/configBuilder.ts +206 -0
- package/vendor-config/utils/loadConfig.ts +16 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import type { ImageMetadata } from 'astro';
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
+
import { getImage } from 'astro:assets';
|
|
4
|
+
import { parseUrl, transformUrl } from 'unpic';
|
|
5
|
+
|
|
6
|
+
type Layout = 'fixed' | 'constrained' | 'fullWidth' | 'cover' | 'responsive' | 'contained';
|
|
7
|
+
|
|
8
|
+
export interface ImageProps extends Omit<HTMLAttributes<'img'>, 'src'> {
|
|
9
|
+
src?: string | ImageMetadata | null;
|
|
10
|
+
width?: string | number | null;
|
|
11
|
+
height?: string | number | null;
|
|
12
|
+
alt?: string | null;
|
|
13
|
+
loading?: 'eager' | 'lazy' | null;
|
|
14
|
+
decoding?: 'sync' | 'async' | 'auto' | null;
|
|
15
|
+
style?: string;
|
|
16
|
+
srcset?: string | null;
|
|
17
|
+
sizes?: string | null;
|
|
18
|
+
fetchpriority?: 'high' | 'low' | 'auto' | null;
|
|
19
|
+
|
|
20
|
+
layout?: Layout;
|
|
21
|
+
widths?: number[] | null;
|
|
22
|
+
aspectRatio?: string | number | null;
|
|
23
|
+
objectPosition?: string;
|
|
24
|
+
|
|
25
|
+
format?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ImagesOptimizer = (
|
|
29
|
+
image: ImageMetadata | string,
|
|
30
|
+
breakpoints: number[],
|
|
31
|
+
width?: number,
|
|
32
|
+
height?: number,
|
|
33
|
+
format?: string
|
|
34
|
+
) => Promise<Array<{ src: string; width: number }>>;
|
|
35
|
+
|
|
36
|
+
/* ******* */
|
|
37
|
+
const config = {
|
|
38
|
+
// FIXME: Use this when image.width is minor than deviceSizes
|
|
39
|
+
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
|
40
|
+
|
|
41
|
+
deviceSizes: [
|
|
42
|
+
640, // older and lower-end phones
|
|
43
|
+
750, // iPhone 6-8
|
|
44
|
+
828, // iPhone XR/11
|
|
45
|
+
960, // older horizontal phones
|
|
46
|
+
1080, // iPhone 6-8 Plus
|
|
47
|
+
1280, // 720p
|
|
48
|
+
1668, // Various iPads
|
|
49
|
+
1920, // 1080p
|
|
50
|
+
2048, // QXGA
|
|
51
|
+
2560, // WQXGA
|
|
52
|
+
3200, // QHD+
|
|
53
|
+
3840, // 4K
|
|
54
|
+
4480, // 4.5K
|
|
55
|
+
5120, // 5K
|
|
56
|
+
6016, // 6K
|
|
57
|
+
],
|
|
58
|
+
|
|
59
|
+
formats: ['image/webp'],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const computeHeight = (width: number, aspectRatio: number) => {
|
|
63
|
+
return Math.floor(width / aspectRatio);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const parseAspectRatio = (aspectRatio: number | string | null | undefined): number | undefined => {
|
|
67
|
+
if (typeof aspectRatio === 'number') {
|
|
68
|
+
return aspectRatio;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof aspectRatio === 'string') {
|
|
72
|
+
const match = aspectRatio.match(/(\d+)\s*[/:]\s*(\d+)/);
|
|
73
|
+
|
|
74
|
+
if (match) {
|
|
75
|
+
const [, num, den] = match.map(Number);
|
|
76
|
+
if (den && !isNaN(num)) {
|
|
77
|
+
return num / den;
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
const numericValue = parseFloat(aspectRatio);
|
|
81
|
+
if (!isNaN(numericValue)) {
|
|
82
|
+
return numericValue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return undefined;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Gets the `sizes` attribute for an image, based on the layout and width
|
|
92
|
+
*/
|
|
93
|
+
export const getSizes = (width?: number, layout?: Layout): string | undefined => {
|
|
94
|
+
if (!width || !layout) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
switch (layout) {
|
|
98
|
+
// If screen is wider than the max size, image width is the max size,
|
|
99
|
+
// otherwise it's the width of the screen
|
|
100
|
+
case 'constrained':
|
|
101
|
+
return `(min-width: ${width}px) ${width}px, 100vw`;
|
|
102
|
+
|
|
103
|
+
// Image is always the same width, whatever the size of the screen
|
|
104
|
+
case 'fixed':
|
|
105
|
+
return `${width}px`;
|
|
106
|
+
|
|
107
|
+
// Image is always the width of the screen
|
|
108
|
+
case 'fullWidth':
|
|
109
|
+
return '100vw';
|
|
110
|
+
|
|
111
|
+
default:
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const pixelate = (value?: number) => (value || value === 0 ? `${value}px` : undefined);
|
|
117
|
+
|
|
118
|
+
const getStyle = ({
|
|
119
|
+
width,
|
|
120
|
+
height,
|
|
121
|
+
aspectRatio,
|
|
122
|
+
layout,
|
|
123
|
+
objectFit = 'cover',
|
|
124
|
+
objectPosition = 'center',
|
|
125
|
+
background,
|
|
126
|
+
}: {
|
|
127
|
+
width?: number;
|
|
128
|
+
height?: number;
|
|
129
|
+
aspectRatio?: number;
|
|
130
|
+
objectFit?: string;
|
|
131
|
+
objectPosition?: string;
|
|
132
|
+
layout?: string;
|
|
133
|
+
background?: string;
|
|
134
|
+
}) => {
|
|
135
|
+
const styleEntries: Array<[prop: string, value: string | undefined]> = [
|
|
136
|
+
['object-fit', objectFit],
|
|
137
|
+
['object-position', objectPosition],
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
// If background is a URL, set it to cover the image and not repeat
|
|
141
|
+
if (background?.startsWith('https:') || background?.startsWith('http:') || background?.startsWith('data:')) {
|
|
142
|
+
styleEntries.push(['background-image', `url(${background})`]);
|
|
143
|
+
styleEntries.push(['background-size', 'cover']);
|
|
144
|
+
styleEntries.push(['background-repeat', 'no-repeat']);
|
|
145
|
+
} else {
|
|
146
|
+
styleEntries.push(['background', background]);
|
|
147
|
+
}
|
|
148
|
+
if (layout === 'fixed') {
|
|
149
|
+
styleEntries.push(['width', pixelate(width)]);
|
|
150
|
+
styleEntries.push(['height', pixelate(height)]);
|
|
151
|
+
styleEntries.push(['object-position', 'top left']);
|
|
152
|
+
}
|
|
153
|
+
if (layout === 'constrained') {
|
|
154
|
+
styleEntries.push(['max-width', pixelate(width)]);
|
|
155
|
+
styleEntries.push(['max-height', pixelate(height)]);
|
|
156
|
+
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
|
|
157
|
+
styleEntries.push(['width', '100%']);
|
|
158
|
+
}
|
|
159
|
+
if (layout === 'fullWidth') {
|
|
160
|
+
styleEntries.push(['width', '100%']);
|
|
161
|
+
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
|
|
162
|
+
styleEntries.push(['height', pixelate(height)]);
|
|
163
|
+
}
|
|
164
|
+
if (layout === 'responsive') {
|
|
165
|
+
styleEntries.push(['width', '100%']);
|
|
166
|
+
styleEntries.push(['height', 'auto']);
|
|
167
|
+
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
|
|
168
|
+
}
|
|
169
|
+
if (layout === 'contained') {
|
|
170
|
+
styleEntries.push(['max-width', '100%']);
|
|
171
|
+
styleEntries.push(['max-height', '100%']);
|
|
172
|
+
styleEntries.push(['object-fit', 'contain']);
|
|
173
|
+
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
|
|
174
|
+
}
|
|
175
|
+
if (layout === 'cover') {
|
|
176
|
+
styleEntries.push(['max-width', '100%']);
|
|
177
|
+
styleEntries.push(['max-height', '100%']);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const styles = Object.fromEntries(styleEntries.filter(([, value]) => value));
|
|
181
|
+
|
|
182
|
+
return Object.entries(styles)
|
|
183
|
+
.map(([key, value]) => `${key}: ${value};`)
|
|
184
|
+
.join(' ');
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const getBreakpoints = ({
|
|
188
|
+
width,
|
|
189
|
+
breakpoints,
|
|
190
|
+
layout,
|
|
191
|
+
}: {
|
|
192
|
+
width?: number;
|
|
193
|
+
breakpoints?: number[];
|
|
194
|
+
layout: Layout;
|
|
195
|
+
}): number[] => {
|
|
196
|
+
if (layout === 'fullWidth' || layout === 'cover' || layout === 'responsive' || layout === 'contained') {
|
|
197
|
+
return breakpoints || config.deviceSizes;
|
|
198
|
+
}
|
|
199
|
+
if (!width) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
const doubleWidth = width * 2;
|
|
203
|
+
if (layout === 'fixed') {
|
|
204
|
+
return [width, doubleWidth];
|
|
205
|
+
}
|
|
206
|
+
if (layout === 'constrained') {
|
|
207
|
+
return [
|
|
208
|
+
// Always include the image at 1x and 2x the specified width
|
|
209
|
+
width,
|
|
210
|
+
doubleWidth,
|
|
211
|
+
// Filter out any resolutions that are larger than the double-res image
|
|
212
|
+
...(breakpoints || config.deviceSizes).filter((w) => w < doubleWidth),
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return [];
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/* ** */
|
|
220
|
+
export const astroAsseetsOptimizer: ImagesOptimizer = async (
|
|
221
|
+
image,
|
|
222
|
+
breakpoints,
|
|
223
|
+
_width,
|
|
224
|
+
_height,
|
|
225
|
+
format = undefined
|
|
226
|
+
) => {
|
|
227
|
+
if (!image) {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return Promise.all(
|
|
232
|
+
breakpoints.map(async (w: number) => {
|
|
233
|
+
const result = await getImage({ src: image, width: w, inferSize: true, ...(format ? { format } : {}) });
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
src: result?.src,
|
|
237
|
+
width: result?.attributes?.width ?? w,
|
|
238
|
+
height: result?.attributes?.height,
|
|
239
|
+
};
|
|
240
|
+
})
|
|
241
|
+
);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const isUnpicCompatible = (image: string) => {
|
|
245
|
+
return typeof parseUrl(image) !== 'undefined';
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/* ** */
|
|
249
|
+
export const unpicOptimizer: ImagesOptimizer = async (image, breakpoints, width, height, format = undefined) => {
|
|
250
|
+
if (!image || typeof image !== 'string') {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const urlParsed = parseUrl(image);
|
|
255
|
+
if (!urlParsed) {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return Promise.all(
|
|
260
|
+
breakpoints.map(async (w: number) => {
|
|
261
|
+
const _height = width && height ? computeHeight(w, width / height) : height;
|
|
262
|
+
const url =
|
|
263
|
+
transformUrl({
|
|
264
|
+
url: image,
|
|
265
|
+
width: w,
|
|
266
|
+
height: _height,
|
|
267
|
+
cdn: urlParsed.cdn,
|
|
268
|
+
...(format ? { format } : {}),
|
|
269
|
+
}) || image;
|
|
270
|
+
return {
|
|
271
|
+
src: String(url),
|
|
272
|
+
width: w,
|
|
273
|
+
height: _height,
|
|
274
|
+
};
|
|
275
|
+
})
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/* ** */
|
|
280
|
+
export async function getImagesOptimized(
|
|
281
|
+
image: ImageMetadata | string,
|
|
282
|
+
{
|
|
283
|
+
src: _,
|
|
284
|
+
width,
|
|
285
|
+
height,
|
|
286
|
+
sizes,
|
|
287
|
+
aspectRatio,
|
|
288
|
+
objectPosition,
|
|
289
|
+
widths,
|
|
290
|
+
layout = 'constrained',
|
|
291
|
+
style = '',
|
|
292
|
+
format,
|
|
293
|
+
...rest
|
|
294
|
+
}: ImageProps,
|
|
295
|
+
transform: ImagesOptimizer = () => Promise.resolve([])
|
|
296
|
+
): Promise<{ src: string; attributes: HTMLAttributes<'img'> }> {
|
|
297
|
+
if (typeof image !== 'string') {
|
|
298
|
+
width ||= Number(image.width) || undefined;
|
|
299
|
+
height ||= typeof width === 'number' ? computeHeight(width, image.width / image.height) : undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
width = (width && Number(width)) || undefined;
|
|
303
|
+
height = (height && Number(height)) || undefined;
|
|
304
|
+
|
|
305
|
+
widths ||= config.deviceSizes;
|
|
306
|
+
sizes ||= getSizes(Number(width) || undefined, layout);
|
|
307
|
+
aspectRatio = parseAspectRatio(aspectRatio);
|
|
308
|
+
|
|
309
|
+
// Calculate dimensions from aspect ratio
|
|
310
|
+
if (aspectRatio) {
|
|
311
|
+
if (width) {
|
|
312
|
+
if (height) {
|
|
313
|
+
/* empty */
|
|
314
|
+
} else {
|
|
315
|
+
height = width / aspectRatio;
|
|
316
|
+
}
|
|
317
|
+
} else if (height) {
|
|
318
|
+
width = Number(height * aspectRatio);
|
|
319
|
+
} else if (layout !== 'fullWidth') {
|
|
320
|
+
// Fullwidth images have 100% width, so aspectRatio is applicable
|
|
321
|
+
console.error('When aspectRatio is set, either width or height must also be set');
|
|
322
|
+
console.error('Image', image);
|
|
323
|
+
}
|
|
324
|
+
} else if (width && height) {
|
|
325
|
+
aspectRatio = width / height;
|
|
326
|
+
} else if (layout !== 'fullWidth') {
|
|
327
|
+
// Fullwidth images don't need dimensions
|
|
328
|
+
console.error('Either aspectRatio or both width and height must be set');
|
|
329
|
+
console.error('Image', image);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let breakpoints = getBreakpoints({ width, breakpoints: widths, layout });
|
|
333
|
+
breakpoints = [...new Set(breakpoints)].sort((a, b) => a - b);
|
|
334
|
+
|
|
335
|
+
const srcset = (await transform(image, breakpoints, Number(width) || undefined, Number(height) || undefined, format))
|
|
336
|
+
.map(({ src, width }) => `${src} ${width}w`)
|
|
337
|
+
.join(', ');
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
src: typeof image === 'string' ? image : image.src,
|
|
341
|
+
attributes: {
|
|
342
|
+
width,
|
|
343
|
+
height,
|
|
344
|
+
srcset: srcset || undefined,
|
|
345
|
+
sizes,
|
|
346
|
+
style: `${getStyle({
|
|
347
|
+
width,
|
|
348
|
+
height,
|
|
349
|
+
aspectRatio,
|
|
350
|
+
objectPosition,
|
|
351
|
+
layout,
|
|
352
|
+
})}${style ?? ''}`,
|
|
353
|
+
...rest,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
package/utils/images.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { astroAsseetsOptimizer, isUnpicCompatible, unpicOptimizer } from './images-optimization';
|
|
2
|
+
|
|
3
|
+
import type { OpenGraph } from '@astrolib/seo';
|
|
4
|
+
import type { ImageMetadata } from 'astro';
|
|
5
|
+
|
|
6
|
+
const load = async function () {
|
|
7
|
+
let images: Record<string, () => Promise<unknown>> | undefined;
|
|
8
|
+
try {
|
|
9
|
+
images = import.meta.glob(
|
|
10
|
+
'/src/assets/images/**/*.{jpeg,jpg,png,tiff,webp,gif,svg,JPEG,JPG,PNG,TIFF,WEBP,GIF,SVG}'
|
|
11
|
+
);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
// continue regardless of error
|
|
14
|
+
console.error(error);
|
|
15
|
+
}
|
|
16
|
+
return images;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
let _images: Record<string, () => Promise<unknown>> | undefined;
|
|
20
|
+
|
|
21
|
+
/** */
|
|
22
|
+
export const fetchLocalImages = async () => {
|
|
23
|
+
_images = _images || (await load());
|
|
24
|
+
return _images;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** */
|
|
28
|
+
export const findImage = async (
|
|
29
|
+
imagePath?: string | ImageMetadata | null
|
|
30
|
+
): Promise<string | ImageMetadata | undefined | null> => {
|
|
31
|
+
// Not string
|
|
32
|
+
if (typeof imagePath !== 'string') {
|
|
33
|
+
return imagePath;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Absolute paths
|
|
37
|
+
if (imagePath.startsWith('http://') || imagePath.startsWith('https://') || imagePath.startsWith('/')) {
|
|
38
|
+
return imagePath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Relative paths or not "~/assets/"
|
|
42
|
+
if (!imagePath.startsWith('~/assets/images')) {
|
|
43
|
+
return imagePath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const images = await fetchLocalImages();
|
|
47
|
+
const key = imagePath.replace('~/', '/src/');
|
|
48
|
+
|
|
49
|
+
return images && typeof images[key] === 'function'
|
|
50
|
+
? ((await images[key]()) as { default: ImageMetadata }).default
|
|
51
|
+
: null;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** */
|
|
55
|
+
export const adaptOpenGraphImages = async (
|
|
56
|
+
openGraph: OpenGraph = {},
|
|
57
|
+
astroSite: URL | undefined = new URL('')
|
|
58
|
+
): Promise<OpenGraph> => {
|
|
59
|
+
if (!openGraph?.images?.length) {
|
|
60
|
+
return openGraph;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const images = openGraph.images;
|
|
64
|
+
const defaultWidth = 1200;
|
|
65
|
+
const defaultHeight = 626;
|
|
66
|
+
|
|
67
|
+
const adaptedImages = await Promise.all(
|
|
68
|
+
images.map(async (image) => {
|
|
69
|
+
if (image?.url) {
|
|
70
|
+
const resolvedImage = (await findImage(image.url)) as ImageMetadata | string | undefined;
|
|
71
|
+
if (!resolvedImage) {
|
|
72
|
+
return {
|
|
73
|
+
url: '',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let _image;
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
typeof resolvedImage === 'string' &&
|
|
81
|
+
(resolvedImage.startsWith('http://') || resolvedImage.startsWith('https://')) &&
|
|
82
|
+
isUnpicCompatible(resolvedImage)
|
|
83
|
+
) {
|
|
84
|
+
_image = (await unpicOptimizer(resolvedImage, [defaultWidth], defaultWidth, defaultHeight, 'jpg'))[0];
|
|
85
|
+
} else if (resolvedImage) {
|
|
86
|
+
const dimensions =
|
|
87
|
+
typeof resolvedImage !== 'string' && resolvedImage?.width <= defaultWidth
|
|
88
|
+
? [resolvedImage?.width, resolvedImage?.height]
|
|
89
|
+
: [defaultWidth, defaultHeight];
|
|
90
|
+
_image = (
|
|
91
|
+
await astroAsseetsOptimizer(resolvedImage, [dimensions[0]], dimensions[0], dimensions[1], 'jpg')
|
|
92
|
+
)[0];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof _image === 'object') {
|
|
96
|
+
return {
|
|
97
|
+
url: 'src' in _image && typeof _image.src === 'string' ? String(new URL(_image.src, astroSite)) : '',
|
|
98
|
+
width: 'width' in _image && typeof _image.width === 'number' ? _image.width : undefined,
|
|
99
|
+
height: 'height' in _image && typeof _image.height === 'number' ? _image.height : undefined,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
url: '',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
url: '',
|
|
109
|
+
};
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return { ...openGraph, ...(adaptedImages ? { images: adaptedImages } : {}) };
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const createImageSrcBySize = (src: string, size: number, format: string = ''): string | undefined => {
|
|
117
|
+
if (!src) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const imageFilename = src.split('/').pop()!;
|
|
122
|
+
const imageBaseName = imageFilename.split('.').slice(0, -1).join('.');
|
|
123
|
+
const imageExt = format || imageFilename.split('.').pop()!;
|
|
124
|
+
|
|
125
|
+
return src.replace(imageFilename, `${imageBaseName}-${size}.${imageExt}`);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const getImagePathBySize = (src: string, size: number, format: string = ''): string => {
|
|
129
|
+
if (!src) {
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const imageFilename = src.split('/').pop()!;
|
|
134
|
+
const imageBaseName = imageFilename.split('.').slice(0, -1).join('.');
|
|
135
|
+
const imageExt = format || imageFilename.split('.').pop()!;
|
|
136
|
+
|
|
137
|
+
return `${src.replace(imageFilename, `${imageBaseName}-${size}.${imageExt}`)}?width=${size}`;
|
|
138
|
+
};
|
package/utils/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import slugify from 'limax';
|
|
2
|
+
import {trim} from './utils';
|
|
3
|
+
|
|
4
|
+
export const trimSlash = (s: string) => trim(trim(s, '/'));
|
|
5
|
+
export const createPath = (...params: string[]) => {
|
|
6
|
+
const paths = params
|
|
7
|
+
.map((el) => trimSlash(el))
|
|
8
|
+
.filter((el) => !!el)
|
|
9
|
+
.join('/');
|
|
10
|
+
return '/' + paths + (paths ? '/' : '');
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const BASE_PATHNAME = '/';
|
|
14
|
+
|
|
15
|
+
export const cleanSlug = (text = '') =>
|
|
16
|
+
trimSlash(text)
|
|
17
|
+
.split('/')
|
|
18
|
+
.map((slug: string) => slugify(slug))
|
|
19
|
+
.join('/');
|
|
20
|
+
|
|
21
|
+
export const getCanonical = (path = ''): string | URL => {
|
|
22
|
+
const url = String(new URL(path, 'https://example.com'));
|
|
23
|
+
if (path && url.endsWith('/')) {
|
|
24
|
+
return url.slice(0, -1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return url;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const getAsset = (path: string): string =>
|
|
31
|
+
'/' +
|
|
32
|
+
[BASE_PATHNAME, path]
|
|
33
|
+
.map((el) => trimSlash(el))
|
|
34
|
+
.filter((el) => !!el)
|
|
35
|
+
.join('/');
|
|
36
|
+
|
|
37
|
+
const definitivePermalink = (permalink: string): string => createPath(BASE_PATHNAME, permalink);
|
|
38
|
+
|
package/utils/utils.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { I18N } from 'vendor:config';
|
|
2
|
+
|
|
3
|
+
export const formatter: Intl.DateTimeFormat = new Intl.DateTimeFormat(I18N?.language, {
|
|
4
|
+
year: 'numeric',
|
|
5
|
+
month: 'short',
|
|
6
|
+
day: 'numeric',
|
|
7
|
+
timeZone: 'UTC',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const getFormattedDate = (date: Date): string => (date ? formatter.format(date) : '');
|
|
11
|
+
|
|
12
|
+
export const trim = (str = '', ch?: string) => {
|
|
13
|
+
let start = 0;
|
|
14
|
+
let end = str.length || 0;
|
|
15
|
+
while (start < end && str[start] === ch) {
|
|
16
|
+
++start;
|
|
17
|
+
}
|
|
18
|
+
while (end > start && str[end - 1] === ch) {
|
|
19
|
+
--end;
|
|
20
|
+
}
|
|
21
|
+
return start > 0 || end < str.length ? str.substring(start, end) : str;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Function to format a number in thousands (K) or millions (M) format depending on its value
|
|
25
|
+
export const toUiAmount = (amount: number) => {
|
|
26
|
+
if (!amount) {
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let value: string;
|
|
31
|
+
|
|
32
|
+
if (amount >= 1000000000) {
|
|
33
|
+
const formattedNumber = (amount / 1000000000).toFixed(1);
|
|
34
|
+
if (Number(formattedNumber) === parseInt(formattedNumber)) {
|
|
35
|
+
value = `${parseInt(formattedNumber)}B`;
|
|
36
|
+
} else {
|
|
37
|
+
value = `${formattedNumber}B`;
|
|
38
|
+
}
|
|
39
|
+
} else if (amount >= 1000000) {
|
|
40
|
+
const formattedNumber = (amount / 1000000).toFixed(1);
|
|
41
|
+
if (Number(formattedNumber) === parseInt(formattedNumber)) {
|
|
42
|
+
value = `${parseInt(formattedNumber)}M`;
|
|
43
|
+
} else {
|
|
44
|
+
value = `${formattedNumber}M`;
|
|
45
|
+
}
|
|
46
|
+
} else if (amount >= 1000) {
|
|
47
|
+
const formattedNumber = (amount / 1000).toFixed(1);
|
|
48
|
+
if (Number(formattedNumber) === parseInt(formattedNumber)) {
|
|
49
|
+
value = `${parseInt(formattedNumber)}K`;
|
|
50
|
+
} else {
|
|
51
|
+
value = `${formattedNumber}K`;
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
value = Number(amount).toFixed(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return value;
|
|
58
|
+
};
|