core-maugli 1.2.63 → 1.2.65

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.63",
5
+ "version": "1.2.65",
6
6
  "license": "GPL-3.0-or-later OR Commercial",
7
7
  "repository": {
8
8
  "type": "git",
Binary file
Binary file
Binary file
@@ -1,14 +1,14 @@
1
- // copy-content-images.cjs - выносим все изображения из подпапок public/img в корень public/img
1
+ // copy-content-images.cjs - Move all images from public/img subfolders to public/img root
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
 
5
5
  const sourceDir = './public/img';
6
6
  const targetDir = './public/img';
7
7
 
8
- // Функция для рекурсивного поиска и копирования изображений из подпапок
8
+ // Function to recursively find and copy images from subfolders
9
9
  async function flattenImages(sourceDir, targetDir) {
10
10
  if (!fs.existsSync(sourceDir)) {
11
- console.log(`📁 Папка ${sourceDir} не существует, пропускаем`);
11
+ console.log(`📁 Folder ${sourceDir} does not exist, skipping`);
12
12
  return 0;
13
13
  }
14
14
 
@@ -20,27 +20,27 @@ async function flattenImages(sourceDir, targetDir) {
20
20
  const stat = fs.statSync(sourcePath);
21
21
 
22
22
  if (stat.isDirectory()) {
23
- // Рекурсивно обрабатываем подпапки
23
+ // Recursively process subfolders
24
24
  const copied = await flattenImages(sourcePath, targetDir);
25
25
  copiedCount += copied;
26
26
  } else if (stat.isFile()) {
27
27
  const ext = path.extname(item).toLowerCase();
28
28
 
29
- // Проверяем, что это изображение и что оно в подпапке (не в корне)
29
+ // Check if it's an image and it's in a subfolder (not in root)
30
30
  if (['.jpg', '.jpeg', '.png', '.webp', '.gif', '.svg'].includes(ext)) {
31
31
  const relativePath = path.relative(sourceDir, sourcePath);
32
32
 
33
- // Если файл не в корне public/img, то копируем его в корень
33
+ // If file is not in public/img root, copy it to root
34
34
  if (relativePath.includes(path.sep)) {
35
35
  const targetPath = path.join(targetDir, item);
36
36
 
37
- // Проверяем, что файл с таким именем не существует в корне
37
+ // Check that file with this name doesn't exist in root
38
38
  if (!fs.existsSync(targetPath)) {
39
39
  fs.copyFileSync(sourcePath, targetPath);
40
- console.log(`📋 Вынесено: ${relativePath} → ${item}`);
40
+ console.log(`📋 Moved: ${relativePath} → ${item}`);
41
41
  copiedCount++;
42
42
  } else {
43
- console.log(`⚠️ Пропущено (уже существует): ${item}`);
43
+ console.log(`⚠️ Skipped (already exists): ${item}`);
44
44
  }
45
45
  }
46
46
  }
@@ -51,16 +51,16 @@ async function flattenImages(sourceDir, targetDir) {
51
51
  }
52
52
 
53
53
  async function main() {
54
- console.log('🚀 Начинаем вынос изображений из подпапок public/img в корень public/img...');
54
+ console.log('🚀 Starting image flattening from public/img subfolders to public/img root...');
55
55
 
56
- // Выносим все изображения из подпапок в корень
56
+ // Move all images from subfolders to root
57
57
  const totalCopied = await flattenImages(sourceDir, targetDir);
58
58
 
59
59
  console.log('');
60
- console.log(`✅ Вынос завершен! Скопировано ${totalCopied} изображений в корень public/img/`);
61
- console.log('🔄 Netlify Image Optimization теперь сможет их легче обрабатывать');
62
- console.log('⚡ Sharp оптимизация также будет применена к файлам в корне');
63
- console.log('📁 Все изображения теперь доступны напрямую из /img/имя_файла.webp');
60
+ console.log(`✅ Flattening completed! Copied ${totalCopied} images to public/img/ root`);
61
+ console.log('🔄 Netlify Image Optimization can now process them more easily');
62
+ console.log('⚡ Sharp optimization will also be applied to files in root');
63
+ console.log('📁 All images are now available directly from /img/filename.webp');
64
64
  }
65
65
 
66
66
  main().catch(console.error);
@@ -1,11 +1,11 @@
1
- // copy-content-images.cjs - выносим все изображения из подпапок public/img в корень public/img
1
+ // flatten-images.cjs - Move all images from public/img subfolders to public/img root
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
 
5
5
  const sourceDir = './public/img';
6
6
  const targetDir = './public/img';
7
7
 
8
- // Функция для рекурсивного поиска и копирования изображений из подпапок
8
+ // Function to recursively find and copy images from subfolders
9
9
  async function flattenImages(currentDir) {
10
10
  const items = fs.readdirSync(currentDir);
11
11
  let copiedCount = 0;
@@ -15,26 +15,26 @@ async function flattenImages(currentDir) {
15
15
  const stat = fs.statSync(itemPath);
16
16
 
17
17
  if (stat.isDirectory()) {
18
- // Рекурсивно обрабатываем подпапки
18
+ // Recursively process subfolders
19
19
  const copied = await flattenImages(itemPath);
20
20
  copiedCount += copied;
21
21
  } else if (stat.isFile()) {
22
22
  const ext = path.extname(item).toLowerCase();
23
23
 
24
- // Проверяем, что это изображение и что оно НЕ в корне public/img
24
+ // Check if it's an image and NOT in public/img root
25
25
  if (['.jpg', '.jpeg', '.png', '.webp', '.gif', '.svg'].includes(ext)) {
26
26
  const isInSubfolder = currentDir !== sourceDir;
27
27
 
28
28
  if (isInSubfolder) {
29
29
  const targetPath = path.join(sourceDir, item);
30
30
 
31
- // Проверяем, что файл с таким именем не существует в корне
31
+ // Check that file with this name doesn't exist in root
32
32
  if (!fs.existsSync(targetPath)) {
33
33
  fs.copyFileSync(itemPath, targetPath);
34
- console.log(`📋 Вынесено: ${path.relative('./public', itemPath)} → img/${item}`);
34
+ console.log(`📋 Moved: ${path.relative('./public', itemPath)} → img/${item}`);
35
35
  copiedCount++;
36
36
  } else {
37
- // Если файл уже существует, добавляем префикс папки
37
+ // If file already exists, add folder prefix
38
38
  const folderName = path.basename(currentDir);
39
39
  const nameWithoutExt = path.parse(item).name;
40
40
  const extension = path.parse(item).ext;
@@ -43,10 +43,10 @@ async function flattenImages(currentDir) {
43
43
 
44
44
  if (!fs.existsSync(targetPathWithPrefix)) {
45
45
  fs.copyFileSync(itemPath, targetPathWithPrefix);
46
- console.log(`📋 Вынесено с префиксом: ${path.relative('./public', itemPath)} → img/${newName}`);
46
+ console.log(`📋 Moved with prefix: ${path.relative('./public', itemPath)} → img/${newName}`);
47
47
  copiedCount++;
48
48
  } else {
49
- console.log(`⚠️ Пропущено (уже существует): ${item}`);
49
+ console.log(`⚠️ Skipped (already exists): ${item}`);
50
50
  }
51
51
  }
52
52
  }
@@ -58,21 +58,21 @@ async function flattenImages(currentDir) {
58
58
  }
59
59
 
60
60
  async function main() {
61
- console.log('🚀 Начинаем вынос изображений из подпапок public/img в корень public/img...');
61
+ console.log('🚀 Starting image flattening from public/img subfolders to public/img root...');
62
62
 
63
63
  if (!fs.existsSync(sourceDir)) {
64
- console.log(`📁 Папка ${sourceDir} не существует!`);
64
+ console.log(`📁 Folder ${sourceDir} does not exist!`);
65
65
  return;
66
66
  }
67
67
 
68
- // Выносим все изображения из подпапок в корень
68
+ // Move all images from subfolders to root
69
69
  const totalCopied = await flattenImages(sourceDir);
70
70
 
71
71
  console.log('');
72
- console.log(`✅ Вынос завершен! Скопировано ${totalCopied} изображений в корень public/img/`);
73
- console.log('🔄 Netlify Image Optimization теперь сможет их легче обрабатывать');
74
- console.log('⚡ Sharp оптимизация также будет применена к файлам в корне');
75
- console.log('📁 Все изображения теперь доступны напрямую из /img/имя_файла.webp');
72
+ console.log(`✅ Flattening completed! Copied ${totalCopied} images to public/img/ root`);
73
+ console.log('🔄 Netlify Image Optimization can now process them more easily');
74
+ console.log('⚡ Sharp optimization will also be applied to files in root');
75
+ console.log('📁 All images are now available directly from /img/filename.webp');
76
76
  }
77
77
 
78
78
  main().catch(console.error);
@@ -84,21 +84,21 @@ async function processDirectory(sourceDir, outputSubDir) {
84
84
  }
85
85
  }
86
86
 
87
- // Основная функция
87
+ // Main function
88
88
  async function generatePreviewsForBuild() {
89
- console.log('🚀 Начинаем генерацию превью для сборки...');
89
+ console.log('🚀 Starting preview generation for build...');
90
90
 
91
- // Обрабатываем системные папки
91
+ // Process system folders
92
92
  await processDirectory(path.join(rootDir, 'public/img/default'), 'img/default');
93
93
  await processDirectory(path.join(rootDir, 'public/img/examples'), 'img/examples');
94
94
 
95
- // Обрабатываем пользовательские изображения, если они есть
95
+ // Process user images if they exist
96
96
  const pageImagesDir = path.join(rootDir, 'public/img/page-images');
97
97
  if (fs.existsSync(pageImagesDir)) {
98
98
  await processDirectory(pageImagesDir, 'img/page-images');
99
99
  }
100
100
 
101
- console.log('✅ Генерация превью для сборки завершена!');
101
+ console.log('✅ Preview generation for build completed!');
102
102
  }
103
103
 
104
104
  generatePreviewsForBuild().catch(console.error);
@@ -99,10 +99,10 @@ function getFileSizeStats(originalPath, optimizedPath) {
99
99
  };
100
100
  }
101
101
 
102
- // Рекурсивная функция для обхода папок
102
+ // Recursive function to traverse folders
103
103
  async function processDirectory(dir) {
104
104
  if (!fs.existsSync(dir)) {
105
- console.log(`📁 Папка ${dir} не существует`);
105
+ console.log(`📁 Folder ${dir} does not exist`);
106
106
  return;
107
107
  }
108
108
 
@@ -113,36 +113,36 @@ async function processDirectory(dir) {
113
113
  const stat = fs.statSync(itemPath);
114
114
 
115
115
  if (stat.isDirectory()) {
116
- // Рекурсивно обрабатываем подпапки
116
+ // Recursively process subfolders
117
117
  await processDirectory(itemPath);
118
118
  } else if (stat.isFile()) {
119
119
  const ext = path.extname(item).toLowerCase();
120
120
  const baseName = path.basename(item, ext);
121
121
 
122
- // Проверяем, что это изображение и не содержит размер в названии
122
+ // Check if it's an image and doesn't contain size in the name
123
123
  if (['.jpg', '.jpeg', '.png', '.webp'].includes(ext)) {
124
- // Пропускаем файлы, которые уже содержат размер (например, image-400.webp)
124
+ // Skip files that already contain size (e.g., image-400.webp)
125
125
  if (!/-\d+$/.test(baseName) && !processedFiles.has(itemPath)) {
126
126
  processedFiles.add(itemPath);
127
127
 
128
- console.log(`🔄 Обрабатываем: ${itemPath}`);
128
+ console.log(`🔄 Processing: ${itemPath}`);
129
129
 
130
- // Сначала оптимизируем оригинал
130
+ // First optimize the original
131
131
  const optimizedOriginal = path.join(path.dirname(itemPath), `${baseName}_optimized${ext}`);
132
132
  await optimizeImage(itemPath, optimizedOriginal);
133
133
 
134
- // Заменяем оригинал оптимизированной версией
134
+ // Replace original with optimized version
135
135
  if (fs.existsSync(optimizedOriginal)) {
136
136
  const stats = getFileSizeStats(itemPath, optimizedOriginal);
137
137
  if (stats && stats.savings > 0) {
138
138
  fs.renameSync(optimizedOriginal, itemPath);
139
- console.log(`💾 Экономия: ${stats.savings}KB (${stats.savingsPercent}%) - ${itemPath}`);
139
+ console.log(`💾 Savings: ${stats.savings}KB (${stats.savingsPercent}%) - ${itemPath}`);
140
140
  } else {
141
141
  fs.unlinkSync(optimizedOriginal);
142
142
  }
143
143
  }
144
144
 
145
- // Создаем ресайзы
145
+ // Create resizes
146
146
  for (const width of sizes) {
147
147
  const outputPath = path.join(path.dirname(itemPath), `${baseName}-${width}${ext}`);
148
148
  await optimizeImage(itemPath, outputPath, width);
@@ -154,18 +154,18 @@ async function processDirectory(dir) {
154
154
  }
155
155
 
156
156
  async function main() {
157
- console.log('🚀 Начинаем оптимизацию изображений с Sharp...');
158
- console.log('⚙️ Настройки оптимизации:');
159
- console.log(' WebP: качество 80, максимальное сжатие');
160
- console.log(' JPEG: качество 85, прогрессивная загрузка');
161
- console.log(' PNG: качество 90, максимальное сжатие');
157
+ console.log('🚀 Starting image optimization with Sharp...');
158
+ console.log('⚙️ Optimization settings:');
159
+ console.log(' WebP: quality 80, maximum compression');
160
+ console.log(' JPEG: quality 85, progressive loading');
161
+ console.log(' PNG: quality 90, maximum compression');
162
162
  console.log('');
163
163
 
164
164
  await processDirectory(inputDir);
165
165
 
166
166
  console.log('');
167
- console.log('✅ Оптимизация завершена!');
168
- console.log('📊 Все изображения оптимизированы для максимальной производительности');
167
+ console.log('✅ Optimization completed!');
168
+ console.log('📊 All images optimized for maximum performance');
169
169
  }
170
170
 
171
171
  main().catch(console.error);
@@ -46,30 +46,30 @@ function createTempDir() {
46
46
  return tempDir;
47
47
  }
48
48
 
49
- // Функция для оптимизации через Squoosh CLI
49
+ // Function to optimize through Squoosh CLI
50
50
  async function optimizeWithSquoosh() {
51
- console.log('🚀 Начинаем оптимизацию через Squoosh CLI...');
51
+ console.log('🚀 Starting optimization through Squoosh CLI...');
52
52
 
53
53
  const images = getAllImages(publicDir);
54
- console.log(`📁 Найдено ${images.length} изображений для оптимизации`);
54
+ console.log(`📁 Found ${images.length} images for optimization`);
55
55
 
56
56
  if (images.length === 0) {
57
- console.log('📷 Нет изображений для оптимизации');
57
+ console.log('📷 No images to optimize');
58
58
  return;
59
59
  }
60
60
 
61
61
  const tempDir = createTempDir();
62
62
 
63
63
  try {
64
- // Создаем директорию для входных файлов
64
+ // Create directory for input files
65
65
  const inputDir = path.join(tempDir, 'input');
66
66
  const outputDir = path.join(tempDir, 'output');
67
67
 
68
68
  if (!fs.existsSync(inputDir)) fs.mkdirSync(inputDir, { recursive: true });
69
69
  if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
70
70
 
71
- // Копируем файлы во временную директорию
72
- console.log('📋 Подготавливаем файлы...');
71
+ // Copy files to temporary directory
72
+ console.log('📋 Preparing files...');
73
73
  images.forEach((imagePath, index) => {
74
74
  const ext = path.extname(imagePath);
75
75
  const tempFileName = `image_${index}${ext}`;
@@ -77,8 +77,8 @@ async function optimizeWithSquoosh() {
77
77
  fs.copyFileSync(imagePath, tempFilePath);
78
78
  });
79
79
 
80
- // Запускаем Squoosh CLI для WebP оптимизации
81
- console.log('⚡ Запускаем Squoosh CLI...');
80
+ // Run Squoosh CLI for WebP optimization
81
+ console.log('⚡ Running Squoosh CLI...');
82
82
  const squooshCommand = `npx @squoosh/cli --webp auto "${inputDir}/*" -d "${outputDir}"`;
83
83
 
84
84
  try {
@@ -87,9 +87,9 @@ async function optimizeWithSquoosh() {
87
87
  cwd: projectRoot
88
88
  });
89
89
 
90
- console.log('✅ Squoosh CLI завершен');
90
+ console.log('✅ Squoosh CLI completed');
91
91
 
92
- // Копируем оптимизированные файлы обратно
92
+ // Copy optimized files back
93
93
  const optimizedFiles = fs.readdirSync(outputDir);
94
94
  let totalSavings = 0;
95
95
  let processedCount = 0;
@@ -1,6 +1,8 @@
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
-
4
6
  import { LANGUAGES } from '../i18n/languages';
5
7
 
6
8
  // Карточка баннера продукта для боковой панели статьи/проекта
@@ -8,10 +10,71 @@ import { LANGUAGES } from '../i18n/languages';
8
10
  const { product } = Astro.props;
9
11
  const { seo, image, title, id, productLink } = product?.data || {};
10
12
 
11
- // Дефолтная картинка всегда
12
- const productID = product?.data?.productID;
13
- let bannerImage = productID ? `/${productID}.webp` : maugliConfig.defaultProductImage;
14
- if (!bannerImage) bannerImage = maugliConfig.defaultProductImage;
13
+ // Функция для получения изображения продукта с приоритетом превью 400px
14
+ function getProductBannerImage(product: any, defaultImage: string): string {
15
+ const { image, productID } = product?.data || {};
16
+
17
+ // Если есть изображение в контенте продукта - используем его с превью логикой
18
+ if (image?.src) {
19
+ const imageSrc = image.src;
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const projectRoot = path.resolve(path.dirname(__filename), '../..');
23
+
24
+ // Получаем путь к файлу и имя файла
25
+ const pathParts = imageSrc.split('/');
26
+ const fileName = pathParts.pop() || '';
27
+ const directory = pathParts.join('/');
28
+ const baseName = fileName.replace(/\.(webp|jpg|jpeg|png)$/i, '');
29
+ const extension = fileName.match(/\.(webp|jpg|jpeg|png)$/i)?.[0] || '.webp';
30
+
31
+ // Приоритет 1: превью 400px версия
32
+ const preview400Path = `${directory}/${baseName}-400${extension}`;
33
+ const preview400FilePath = path.join(projectRoot, 'public', preview400Path.replace(/^\//, ''));
34
+ if (fs.existsSync(preview400FilePath)) {
35
+ return preview400Path;
36
+ }
37
+
38
+ // Приоритет 2: превью версия в папке previews
39
+ const previewPath = `${directory}/previews/${fileName}`;
40
+ const previewFilePath = path.join(projectRoot, 'public', previewPath.replace(/^\//, ''));
41
+ if (fs.existsSync(previewFilePath)) {
42
+ return previewPath;
43
+ }
44
+
45
+ // Приоритет 3: оригинальное изображение из контента
46
+ const originalFilePath = path.join(projectRoot, 'public', imageSrc.replace(/^\//, ''));
47
+ if (fs.existsSync(originalFilePath)) {
48
+ return imageSrc;
49
+ }
50
+ }
51
+
52
+ // Fallback: если нет изображения в контенте, пробуем по productID (старая логика)
53
+ if (productID) {
54
+ const __filename = fileURLToPath(import.meta.url);
55
+ const projectRoot = path.resolve(path.dirname(__filename), '../..');
56
+
57
+ // Проверяем стандартные места для productID
58
+ const possiblePaths = [
59
+ `/img/default/previews/${productID}-400.webp`,
60
+ `/img/default/previews/${productID}.webp`,
61
+ `/${productID}.webp`,
62
+ `/img/default/${productID}.webp`
63
+ ];
64
+
65
+ for (const testPath of possiblePaths) {
66
+ const testFilePath = path.join(projectRoot, 'public', testPath.replace(/^\//, ''));
67
+ if (fs.existsSync(testFilePath)) {
68
+ return testPath;
69
+ }
70
+ }
71
+ }
72
+
73
+ // Финальный fallback: дефолтное изображение
74
+ return defaultImage;
75
+ }
76
+
77
+ const bannerImage = getProductBannerImage(product, maugliConfig.defaultProductImage);
15
78
  // Кнопка: если есть productLink, то ссылка, иначе неактивная
16
79
  const buttonHref = productLink && productLink.trim() ? productLink : null;
17
80
  const isExternal = buttonHref && /^https?:\/\//.test(buttonHref);
@@ -1,35 +1,35 @@
1
1
  ---
2
- title: "Rubrics"
3
- slug: "rubrics"
2
+ title: "Рубрики и теги"
3
+ slug: "tags"
4
4
  image:
5
5
  src: "/defaultRubricImage.webp"
6
6
  seo:
7
- title: "Maugli Rubrics Key Topics and Content Categories"
8
- description: "Explore Maugli Rubrics: curated topics and categories for those interested in AI, process automation, content marketing, and cutting-edge technologies."
9
- keywords: ["rubrics", "topics", "categories", "AI", "content automation", "content marketing"]
7
+ title: "Рубрики Maugli — ключевые темы и категории контента"
8
+ description: "Рубрики Maugli: от ИИ и автоматизации процессов до контент-маркетинга и GPT-SEO. Подборки по темам, основанные на реальных трендах и проверенных источниках."
9
+ keywords: ["рубрики", "теги", "темы", "категории", "ИИ", "AI", "автоматизация контента", "контент-маркетинг", "GPT-SEO", "Generated Engine Optimization"]
10
10
  jsonld:
11
11
  "@context": "https://schema.org"
12
12
  "@type": "CollectionPage"
13
- name: "Maugli Rubrics"
14
- url: "https://blog.maugli.cfd/en/rubrics"
15
- description: "Maugli Rubrics curated topics and categories with content focused on automation, AI, and business-driven strategies."
13
+ name: "Рубрики Maugli"
14
+ url: "https://blogru.maugli.cfd/tags"
15
+ description: "Подборки материалов по ключевым темам: ИИ, автоматизация, контент-стратегия, GPT-SEO и дистрибуция. Основано на реальных трендах и проверенных данных."
16
16
  publisher:
17
17
  "@type": "Organization"
18
- name: "Maugli Editorial Platform"
18
+ name: "Редакционная платформа Maugli"
19
19
  logo:
20
20
  "@type": "ImageObject"
21
- url: "https://blog.maugli.cfd/logoblog-icon.svg"
22
- inLanguage: "en"
21
+ url: "https://blogru.maugli.cfd/logoblog-icon.svg"
22
+ inLanguage: "ru"
23
23
  ---
24
24
 
25
+ Хотя контент **Maugli** — это демо-витрина, все рубрики и статьи собраны из **живых трендов, актуальных тем и проверенных источников**.
25
26
 
26
- Although **Maugli** content serves as a demo showcase, all rubrics and articles are based on **real trends, relevant topics, and verified data**.
27
+ Эта страница поможет понять, какие темы и направления мы освещаем, и быстро найти материалы по интересующим вопросам.:
28
+ - Хотите автоматизировать производство контента и маркетинговые процессы;
29
+ - Ищете рабочие способы встроить ИИ в свой пайплайн;
30
+ - Выстраиваете современную контент-стратегию, которая индексируется и людьми, и нейросетями.
27
31
 
28
- Our rubrics are designed for those who:
29
- - Want to automate their content and marketing workflows.
30
- - Seek powerful tools to integrate AI into their processes.
31
- - Are interested in modern content strategy practices.
32
+ Каждая рубрика это **структурированный пул материалов**, созданный на стыке **нейросетевой аналитики и редакторской экспертизы**: от практики GPT-SEO (Generated Engine Optimization) до дистрибуции по Telegram, Reddit, Medium и RSS.
32
33
 
33
- Each section offers structured and valuable content, created at the intersection of **neural networks and expert editorial insight**.
34
+ > «Рубрики Maugli помогают быстро находить то, что интересует.»
34
35
 
35
- > “Maugli Rubrics help you quickly find the content that truly works for your business.”
@@ -5,6 +5,9 @@ publishDate: 2024-01-01
5
5
  updatedDate: 2024-01-01
6
6
  isFeatured: false
7
7
  tags: ['demo', 'example']
8
+ image:
9
+ src: '/img/examples/projects/example_project.webp'
10
+ alt: 'Example Project Demo'
8
11
  seo:
9
12
  title: 'Example Project'
10
13
  description: 'Demo project for showcase purposes.'
@@ -3,6 +3,9 @@ title: Maugli Content Farms
3
3
  description: ИИ-редакция полного цикла для создания контента блогов, телеграм-каналов и соцсетей
4
4
  publishDate: '2025-05-11'
5
5
  tags: ['ИИ', 'Автоматизация', 'Контент', 'Мауgли']
6
+ image:
7
+ src: '/img/examples/projects/project_1.webp'
8
+ alt: 'Maugli Content Farms - ИИ-редакция полного цикла'
6
9
  productID: 'blackbox'
7
10
  productLink: 'https://google.com'
8
11
  generativeEngineOptimization:
package/src/i18n/en.json CHANGED
@@ -37,7 +37,7 @@
37
37
  "title": "Categories",
38
38
  "description": "All articles by category",
39
39
  "blogRubrics": "Blog categories",
40
- "moreTags": "More tags"
40
+ "moreTags": "Tags"
41
41
  },
42
42
  "index": {
43
43
  "title": "<Blog>",
package/src/i18n/es.json CHANGED
@@ -37,7 +37,7 @@
37
37
  "title": "Categorías",
38
38
  "description": "Todos los artículos por categoría",
39
39
  "blogRubrics": "Categorías del blog",
40
- "moreTags": "Más etiquetas"
40
+ "moreTags": "Etiquetas"
41
41
  },
42
42
  "index": {
43
43
  "title": "<Blog>",
@@ -106,7 +106,7 @@
106
106
  "tagsSection": {
107
107
  "allCases": "Todos los casos",
108
108
  "allArticles": "Todos los artículos",
109
- "allTags": "Todas las etiquetas",
109
+ "allTags": "Etiquetas",
110
110
  "rubricAlt": "Categoría",
111
111
  "tagAriaLabel": "Etiqueta: {name} ({count})"
112
112
  },
package/src/i18n/fr.json CHANGED
@@ -37,7 +37,7 @@
37
37
  "title": "Catégories",
38
38
  "description": "Tous les articles par catégorie",
39
39
  "blogRubrics": "Catégories du blog",
40
- "moreTags": "Plus de tags"
40
+ "moreTags": "Tags"
41
41
  },
42
42
  "index": {
43
43
  "title": "<Blog>",
@@ -106,7 +106,7 @@
106
106
  "tagsSection": {
107
107
  "allCases": "Tous les cas",
108
108
  "allArticles": "Tous les articles",
109
- "allTags": "Tous les tags",
109
+ "allTags": "Tags",
110
110
  "rubricAlt": "Catégorie",
111
111
  "tagAriaLabel": "Tag : {name} ({count})"
112
112
  },
package/src/i18n/ja.json CHANGED
@@ -37,7 +37,7 @@
37
37
  "title": "カテゴリ",
38
38
  "description": "カテゴリごとの記事一覧",
39
39
  "blogRubrics": "ブログカテゴリ",
40
- "moreTags": "その他のタグ"
40
+ "moreTags": "タグ"
41
41
  },
42
42
  "index": {
43
43
  "title": "<ブログ>",
package/src/i18n/ru.json CHANGED
@@ -34,7 +34,7 @@
34
34
  "title": "Рубрики",
35
35
  "description": "Все статьи по рубрикам",
36
36
  "blogRubrics": "Рубрики блога",
37
- "moreTags": "Дополнительные теги"
37
+ "moreTags": "Теги"
38
38
  },
39
39
  "index": {
40
40
  "title": "<Блог>",
package/src/i18n/zh.json CHANGED
@@ -37,7 +37,7 @@
37
37
  "title": "分类",
38
38
  "description": "按类别查看所有文章",
39
39
  "blogRubrics": "博客分类",
40
- "moreTags": "更多标签"
40
+ "moreTags": "标签"
41
41
  },
42
42
  "index": {
43
43
  "title": "<博客>",
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  import { type CollectionEntry, render } from 'astro:content';
3
- import { getFilteredCollection } from '../../utils/content-loader';
4
3
  import ArticleMeta from '../../components/ArticleMeta.astro';
5
4
  import ContentFooter from '../../components/ContentFooter.astro';
6
5
  import HeroImage from '../../components/HeroImage.astro';
@@ -12,6 +11,7 @@ import TableOfContents from '../../components/TableOfContents.astro';
12
11
  import TagsAndShare from '../../components/TagsAndShare.astro';
13
12
  import { maugliConfig } from '../../config/maugli.config';
14
13
  import BaseLayout from '../../layouts/BaseLayout.astro';
14
+ import { getFilteredCollection } from '../../utils/content-loader';
15
15
  import { sortItemsByDateDesc } from '../../utils/data-utils';
16
16
  import { calculateReadingTime } from '../../utils/reading-time';
17
17
 
@@ -117,8 +117,9 @@ if (post.data.productID) {
117
117
  product={{
118
118
  data: {
119
119
  productLink: post.data.productLink,
120
- image: bannerImage,
121
- title: post.data.title || 'Продукт'
120
+ image: post.data.image || { src: maugliConfig.seo.defaultImage, alt: post.data.title || 'Продукт' },
121
+ title: post.data.title || 'Продукт',
122
+ productID: post.data.productID
122
123
  }
123
124
  }}
124
125
  />
@@ -105,11 +105,9 @@ const moreByTag = pages.projects?.moreByTag || 'More cases';
105
105
  product={{
106
106
  data: {
107
107
  productLink: project.data.productLink,
108
- image: {
109
- src: project.data.productID ? `/public/${project.data.productID}.webp` : maugliConfig.seo.defaultImage,
110
- alt: project.data.title || 'Продукт'
111
- },
112
- title: project.data.title || 'Продукт'
108
+ image: project.data.image || { src: maugliConfig.seo.defaultImage, alt: project.data.title || 'Продукт' },
109
+ title: project.data.title || 'Продукт',
110
+ productID: project.data.productID
113
111
  }
114
112
  }}
115
113
  />