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
@@ -2,7 +2,7 @@
2
2
  "name": "core-maugli",
3
3
  "description": "Astro & Tailwind CSS blog theme for Maugli.",
4
4
  "type": "module",
5
- "version": "1.2.25",
5
+ "version": "1.2.26",
6
6
  "license": "GPL-3.0-or-later OR Commercial",
7
7
  "repository": {
8
8
  "type": "git",
@@ -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 { getAbsoluteLocaleUrl, getRelativeLocaleUrl } from 'astro:i18n';
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.toString()} />}
180
- {typeof resolvedImage?.height !== 'undefined' && <meta property="og:image:height" content={resolvedImage.height.toString()} />}
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
- // Убеждаемся, что получаем только строку пути, а не объект asset
34
- let baseImage =
27
+ const baseImage =
35
28
  seo?.image?.src ||
36
- (image && typeof image === 'object' && image.src ? image.src : image && typeof image === 'string' ? image : undefined) ||
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
- // Убеждаемся, что baseImage - это строка, а не asset объект
46
- if (baseImage && typeof baseImage === 'object' && baseImage.src) {
47
- baseImage = baseImage.src;
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
- // Убираем ведущий слэш, чтобы избежать обработки через asset pipeline
51
- if (baseImage && baseImage.startsWith('/')) {
52
- baseImage = baseImage.substring(1);
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
- console.log('Card.astro baseImage:', baseImage);
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 cardImageAlt = seo?.image?.alt || (image && typeof image === 'object' ? image.alt : undefined) || title || 'Изображение';
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={baseImage}
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
- <div class="flex flex-col items-start gap-1">
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} />