core-maugli 1.2.25 → 1.2.26
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/package.json
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
---
|
2
|
-
import type { CollectionEntry } from 'astro:content';
|
3
|
-
import '../styles/global.css';
|
4
2
|
import { getFilteredCollection } from '../utils/content-loader';
|
5
|
-
import siteConfig from '../data/site-config';
|
6
|
-
import
|
3
|
+
import siteConfig from '../data/site-config.ts';
|
4
|
+
import '../styles/global.css';
|
7
5
|
// Определи тип выше:
|
8
6
|
export type ImageMimeType = 'image/jpeg' | 'image/png' | 'image/webp';
|
9
7
|
|
@@ -88,72 +86,6 @@ let defaultAuthorName = defaultAuthor.data.name;
|
|
88
86
|
<meta name="robots" content="index, follow" />
|
89
87
|
<meta name="generator" content={Astro.generator} />
|
90
88
|
|
91
|
-
<!-- Critical CSS Inline for Performance -->
|
92
|
-
<style>
|
93
|
-
:root {
|
94
|
-
--brand-color-rgb: 12, 191, 17;
|
95
|
-
--brand-color: rgb(var(--brand-color-rgb));
|
96
|
-
--text-main: #111c2d;
|
97
|
-
--text-heading: #3b5174;
|
98
|
-
--text-muted: #6b7280;
|
99
|
-
--bg-main: #ffffff;
|
100
|
-
--bg-muted: rgba(237, 241, 247, 0.621);
|
101
|
-
--border-main: rgba(17, 28, 44, 0.13);
|
102
|
-
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
103
|
-
--font-serif: 'Geologica', Georgia, 'Times New Roman', serif;
|
104
|
-
}
|
105
|
-
html.dark {
|
106
|
-
--brand-color-rgb: 13, 211, 18;
|
107
|
-
--brand-color: rgb(var(--brand-color-rgb));
|
108
|
-
--text-main: #ffffff;
|
109
|
-
--text-heading: rgba(202, 252, 254, 0.7);
|
110
|
-
--text-muted: #9ca3af;
|
111
|
-
--bg-main: #0b131e;
|
112
|
-
--bg-muted: #131d2cde;
|
113
|
-
--border-main: rgba(51, 66, 66, 0.6);
|
114
|
-
}
|
115
|
-
body {
|
116
|
-
font-family: var(--font-sans);
|
117
|
-
color: var(--text-main);
|
118
|
-
background-color: var(--bg-main);
|
119
|
-
margin: 0;
|
120
|
-
line-height: 1.5;
|
121
|
-
}
|
122
|
-
* {
|
123
|
-
box-sizing: border-box;
|
124
|
-
}
|
125
|
-
</style>
|
126
|
-
|
127
|
-
<!-- Critical Resource Preloading for Performance -->
|
128
|
-
<link rel="preload" href="/favicon.svg" as="image" type="image/svg+xml" />
|
129
|
-
<link rel="preload" href="/footerlabel.svg" as="image" type="image/svg+xml" />
|
130
|
-
<link rel="dns-prefetch" href="https://fonts.googleapis.com" />
|
131
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />
|
132
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
133
|
-
|
134
|
-
<!-- Image Optimization Hints -->
|
135
|
-
<meta name="format-detection" content="telephone=no" />
|
136
|
-
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
|
137
|
-
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
138
|
-
|
139
|
-
<!-- Font Loading Strategy - Non-blocking -->
|
140
|
-
<link
|
141
|
-
rel="preload"
|
142
|
-
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
|
143
|
-
as="style"
|
144
|
-
onload="this.onload=null;this.rel='stylesheet'"
|
145
|
-
/>
|
146
|
-
<link
|
147
|
-
rel="preload"
|
148
|
-
href="https://fonts.googleapis.com/css2?family=Geologica:wght@400;500;600&display=swap"
|
149
|
-
as="style"
|
150
|
-
onload="this.onload=null;this.rel='stylesheet'"
|
151
|
-
/>
|
152
|
-
<noscript>
|
153
|
-
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" />
|
154
|
-
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geologica:wght@400;500;600&display=swap" />
|
155
|
-
</noscript>
|
156
|
-
|
157
89
|
<!-- SEO -->
|
158
90
|
<meta name="description" content={seoDescription} />
|
159
91
|
<meta name="keywords" content={seoKeywords} />
|
@@ -176,8 +108,8 @@ let defaultAuthorName = defaultAuthor.data.name;
|
|
176
108
|
<meta property="og:description" content={seoDescription} />
|
177
109
|
{resolvedImage?.src && <meta property="og:image" content={resolvedImage.src} />}
|
178
110
|
{resolvedImage?.alt && <meta property="og:image:alt" content={resolvedImage.alt} />}
|
179
|
-
{typeof resolvedImage?.width !== 'undefined' && <meta property="og:image:width" content={resolvedImage.width
|
180
|
-
{typeof resolvedImage?.height !== 'undefined' && <meta property="og:image:height" content={resolvedImage.height
|
111
|
+
{typeof resolvedImage?.width !== 'undefined' && <meta property="og:image:width" content={resolvedImage.width} />}
|
112
|
+
{typeof resolvedImage?.height !== 'undefined' && <meta property="og:image:height" content={resolvedImage.height} />}
|
181
113
|
{resolvedImage?.type && <meta property="og:image:type" content={resolvedImage.type} />}
|
182
114
|
|
183
115
|
<!-- X/Twitter -->
|
@@ -1,4 +1,7 @@
|
|
1
1
|
---
|
2
|
+
import fs from 'fs';
|
3
|
+
import path from 'path';
|
4
|
+
import { fileURLToPath } from 'url';
|
2
5
|
import { maugliConfig } from '../config/maugli.config';
|
3
6
|
import FormattedDate from './FormattedDate.astro';
|
4
7
|
|
@@ -13,27 +16,17 @@ type Props = {
|
|
13
16
|
headingLevel?: 'h2' | 'h3';
|
14
17
|
isFeatured?: boolean;
|
15
18
|
class?: string;
|
16
|
-
type?: string;
|
19
|
+
type?: string; // добавлен тип
|
17
20
|
};
|
18
21
|
|
19
22
|
const { href, title, image, seo, publishDate, excerpt, description, headingLevel = 'h2', isFeatured = false, class: className, type } = Astro.props;
|
20
23
|
|
21
24
|
const TitleTag = headingLevel;
|
22
25
|
|
23
|
-
// Отладка: проверяем, что именно приходит в image
|
24
|
-
console.log('Card.astro DEBUG:', {
|
25
|
-
type: typeof image,
|
26
|
-
value: image,
|
27
|
-
src: image?.src,
|
28
|
-
isObject: typeof image === 'object',
|
29
|
-
isString: typeof image === 'string'
|
30
|
-
});
|
31
|
-
|
32
26
|
// Определяем базовое изображение для карточки
|
33
|
-
|
34
|
-
let baseImage =
|
27
|
+
const baseImage =
|
35
28
|
seo?.image?.src ||
|
36
|
-
(image && typeof image === '
|
29
|
+
(image && typeof image.src === 'string' && image.src.length > 0 ? image.src : undefined) ||
|
37
30
|
(type === 'blog'
|
38
31
|
? maugliConfig.defaultBlogImage
|
39
32
|
: type === 'project'
|
@@ -42,19 +35,61 @@ let baseImage =
|
|
42
35
|
? maugliConfig.defaultProductImage
|
43
36
|
: maugliConfig.seo.defaultImage);
|
44
37
|
|
45
|
-
//
|
46
|
-
|
47
|
-
|
48
|
-
|
38
|
+
// Генерируем адаптивные версии изображений
|
39
|
+
function getResponsiveImages(imagePath: string) {
|
40
|
+
const basePath = imagePath.replace(/\.(webp|jpg|jpeg|png)$/i, '');
|
41
|
+
const extension = imagePath.match(/\.(webp|jpg|jpeg|png)$/i)?.[0] || '.webp';
|
42
|
+
|
43
|
+
// Проверяем существование адаптивных версий
|
44
|
+
const __filename = fileURLToPath(import.meta.url);
|
45
|
+
const projectRoot = path.resolve(path.dirname(__filename), '../..');
|
46
|
+
|
47
|
+
const variants = [
|
48
|
+
{ suffix: '-400', width: '400w' },
|
49
|
+
{ suffix: '-800', width: '800w' },
|
50
|
+
{ suffix: '-1200', width: '1200w' }
|
51
|
+
];
|
52
|
+
|
53
|
+
const srcsetItems = [];
|
54
|
+
|
55
|
+
// Добавляем адаптивные версии, если они существуют
|
56
|
+
for (const variant of variants) {
|
57
|
+
const variantPath = `${basePath}${variant.suffix}${extension}`;
|
58
|
+
const filePath = path.join(projectRoot, 'public', variantPath.replace(/^\//, ''));
|
59
|
+
|
60
|
+
if (fs.existsSync(filePath)) {
|
61
|
+
srcsetItems.push(`${variantPath} ${variant.width}`);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
// Всегда добавляем оригинальное изображение
|
66
|
+
srcsetItems.push(`${imagePath} 1200w`);
|
49
67
|
|
50
|
-
|
51
|
-
|
52
|
-
|
68
|
+
return {
|
69
|
+
src: imagePath,
|
70
|
+
srcset: srcsetItems.join(', '),
|
71
|
+
sizes: '(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 400px'
|
72
|
+
};
|
53
73
|
}
|
54
74
|
|
55
|
-
|
75
|
+
const cardImage = getResponsiveImages(baseImage);
|
76
|
+
const cardImageAlt = seo?.image?.alt || image?.alt || title || 'Изображение';
|
77
|
+
|
78
|
+
// Используем уменьшенное превью, если оно существует
|
79
|
+
let previewImage;
|
80
|
+
if (cardImage.src) {
|
81
|
+
previewImage = cardImage.src.replace(/\/([^\/]+)$/, '/previews/$1');
|
82
|
+
|
83
|
+
const __filename = fileURLToPath(import.meta.url);
|
84
|
+
const projectRoot = path.resolve(path.dirname(__filename), '../..');
|
85
|
+
const previewFilePath = path.join(projectRoot, 'public', previewImage.replace(/^\//, ''));
|
86
|
+
|
87
|
+
if (!fs.existsSync(previewFilePath)) {
|
88
|
+
previewImage = undefined;
|
89
|
+
}
|
90
|
+
}
|
56
91
|
|
57
|
-
const
|
92
|
+
const finalImage = previewImage || cardImage.src;
|
58
93
|
|
59
94
|
// Определяем контент для отображения
|
60
95
|
const content = excerpt || description;
|
@@ -71,7 +106,9 @@ const content = excerpt || description;
|
|
71
106
|
<!-- Изображение -->
|
72
107
|
<div class="w-full aspect-[1200/630] bg-[var(--bg-muted)] overflow-hidden relative">
|
73
108
|
<img
|
74
|
-
src={
|
109
|
+
src={finalImage}
|
110
|
+
srcset={cardImage.srcset}
|
111
|
+
sizes={cardImage.sizes}
|
75
112
|
alt={cardImageAlt}
|
76
113
|
loading="lazy"
|
77
114
|
width="1200"
|
@@ -9,7 +9,7 @@ for (const lang of LANGUAGES) {
|
|
9
9
|
dicts[lang.code] = await import(`../i18n/${lang.code}.json`).then((m) => m.default);
|
10
10
|
} catch {}
|
11
11
|
}
|
12
|
-
const lang = maugliConfig.defaultLang || 'en';
|
12
|
+
const lang = maugliConfig.lang || maugliConfig.defaultLang || 'en';
|
13
13
|
const dict = dicts[lang] || dicts['en'] || {};
|
14
14
|
|
15
15
|
const navLinks = (maugliConfig.navLinks || []).map((link) => ({
|
@@ -109,18 +109,7 @@ const copyrightYears = yearStart === yearEnd ? `${yearStart}` : `${yearStart}–
|
|
109
109
|
]}
|
110
110
|
>
|
111
111
|
<div class="flex flex-row justify-between items-end w-full mt-2">
|
112
|
-
<
|
113
|
-
<img src="/footerlabel.svg" alt="Maugli Label" style="height:32px;width:auto;" loading="lazy" decoding="async" />
|
114
|
-
<a
|
115
|
-
href="https://www.npmjs.com/package/core-maugli"
|
116
|
-
target="_blank"
|
117
|
-
rel="noopener noreferrer"
|
118
|
-
class="footer-link text-xs opacity-60 hover:opacity-100"
|
119
|
-
style="color:var(--text-muted);font-family:var(--font-sans);"
|
120
|
-
>
|
121
|
-
npm package
|
122
|
-
</a>
|
123
|
-
</div>
|
112
|
+
<img src="/footerlabel.svg" alt="Maugli Label" style="height:32px;width:auto;" loading="lazy" decoding="async" />
|
124
113
|
{
|
125
114
|
Object.values(maugliConfig.links || {}).some(Boolean) && (
|
126
115
|
<div class="flex flex-row items-center gap-2 ml-4">
|
@@ -1,51 +0,0 @@
|
|
1
|
-
---
|
2
|
-
export interface Props {
|
3
|
-
src: string;
|
4
|
-
alt: string;
|
5
|
-
width?: number;
|
6
|
-
height?: number;
|
7
|
-
class?: string;
|
8
|
-
loading?: 'lazy' | 'eager';
|
9
|
-
decoding?: 'async' | 'sync' | 'auto';
|
10
|
-
sizes?: string;
|
11
|
-
priority?: boolean;
|
12
|
-
quality?: number;
|
13
|
-
}
|
14
|
-
|
15
|
-
const { src, alt, width, height, class: className = '', loading = 'lazy', decoding = 'async', sizes, priority = false, quality = 75, ...rest } = Astro.props;
|
16
|
-
|
17
|
-
// Generate responsive image variations with quality optimization
|
18
|
-
const generateSrcSet = (baseSrc: string) => {
|
19
|
-
const baseUrl = baseSrc.replace(/\.[^.]+$/, '');
|
20
|
-
const ext = 'webp'; // Always use WebP for better compression
|
21
|
-
|
22
|
-
const variations = [
|
23
|
-
{ width: 400, suffix: '-400' },
|
24
|
-
{ width: 800, suffix: '-800' },
|
25
|
-
{ width: 1200, suffix: '-1200' }
|
26
|
-
];
|
27
|
-
|
28
|
-
return variations.map(({ width, suffix }) => `${baseUrl}${suffix}.${ext} ${width}w`).join(', ');
|
29
|
-
};
|
30
|
-
|
31
|
-
const srcSet = generateSrcSet(src);
|
32
|
-
const defaultSizes = sizes || '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw';
|
33
|
-
|
34
|
-
// For priority images, we'll preload them
|
35
|
-
const shouldPreload = priority && loading === 'eager';
|
36
|
-
---
|
37
|
-
|
38
|
-
<img
|
39
|
-
src={src}
|
40
|
-
srcset={srcSet}
|
41
|
-
sizes={defaultSizes}
|
42
|
-
alt={alt}
|
43
|
-
width={width}
|
44
|
-
height={height}
|
45
|
-
class={className}
|
46
|
-
loading={priority ? 'eager' : loading}
|
47
|
-
decoding={decoding}
|
48
|
-
{...rest}
|
49
|
-
/>
|
50
|
-
|
51
|
-
{priority && <link rel="preload" as="image" href={src} type="image/webp" />}
|
@@ -1,46 +0,0 @@
|
|
1
|
-
---
|
2
|
-
export interface Props {
|
3
|
-
src: string;
|
4
|
-
alt: string;
|
5
|
-
width?: number;
|
6
|
-
height?: number;
|
7
|
-
class?: string;
|
8
|
-
loading?: 'lazy' | 'eager';
|
9
|
-
decoding?: 'async' | 'sync' | 'auto';
|
10
|
-
sizes?: string;
|
11
|
-
quality?: number;
|
12
|
-
format?: 'webp' | 'avif' | 'jpeg' | 'png';
|
13
|
-
}
|
14
|
-
|
15
|
-
const {
|
16
|
-
src,
|
17
|
-
alt,
|
18
|
-
width = 1200,
|
19
|
-
height = 630,
|
20
|
-
class: className = '',
|
21
|
-
loading = 'lazy',
|
22
|
-
decoding = 'async',
|
23
|
-
sizes = '(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 400px',
|
24
|
-
quality = 75,
|
25
|
-
format = 'webp',
|
26
|
-
...rest
|
27
|
-
} = Astro.props;
|
28
|
-
|
29
|
-
// Простая стратегия - используем базовое изображение и не генерируем srcset для несуществующих файлов
|
30
|
-
const optimizedSrc = src;
|
31
|
-
|
32
|
-
// Генерируем srcset только если есть базовое изображение
|
33
|
-
const generateSrcSet = (baseSrc: string) => {
|
34
|
-
if (baseSrc.startsWith('http')) {
|
35
|
-
return baseSrc;
|
36
|
-
}
|
37
|
-
|
38
|
-
// Возвращаем только базовое изображение без попыток создания srcset
|
39
|
-
// Responsive варианты будут обработаны build процессом
|
40
|
-
return baseSrc;
|
41
|
-
};
|
42
|
-
|
43
|
-
const srcSet = generateSrcSet(src);
|
44
|
-
---
|
45
|
-
|
46
|
-
<img src={optimizedSrc} alt={alt} width={width} height={height} class={className} loading={loading} decoding={decoding} sizes={sizes} {...rest} />
|