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 +1 -1
- package/public/blackbox-1200.webp +0 -0
- package/public/blackbox-400.webp +0 -0
- package/public/blackbox-800.webp +0 -0
- package/scripts/copy-content-images.cjs +15 -15
- package/scripts/flatten-images.cjs +16 -16
- package/scripts/generate-previews-build.js +5 -5
- package/scripts/optimize-images.cjs +17 -17
- package/scripts/squoosh-optimize.js +11 -11
- package/src/components/ProductBannerCard.astro +68 -5
- package/src/content/pages/rubrics.mdx +18 -18
- package/src/content/projects/example-project.md +3 -0
- package/src/content/projects/project-1.md +3 -0
- package/src/i18n/en.json +1 -1
- package/src/i18n/es.json +2 -2
- package/src/i18n/fr.json +2 -2
- package/src/i18n/ja.json +1 -1
- package/src/i18n/ru.json +1 -1
- package/src/i18n/zh.json +1 -1
- package/src/pages/blog/[id].astro +4 -3
- package/src/pages/projects/[id].astro +3 -5
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// copy-content-images.cjs -
|
|
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(`📁
|
|
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
|
-
//
|
|
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(`📋
|
|
40
|
+
console.log(`📋 Moved: ${relativePath} → ${item}`);
|
|
41
41
|
copiedCount++;
|
|
42
42
|
} else {
|
|
43
|
-
console.log(`⚠️
|
|
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('🚀
|
|
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(`✅
|
|
61
|
-
console.log('🔄 Netlify Image Optimization
|
|
62
|
-
console.log('⚡ Sharp
|
|
63
|
-
console.log('📁
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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(`📋
|
|
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(`📋
|
|
46
|
+
console.log(`📋 Moved with prefix: ${path.relative('./public', itemPath)} → img/${newName}`);
|
|
47
47
|
copiedCount++;
|
|
48
48
|
} else {
|
|
49
|
-
console.log(`⚠️
|
|
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('🚀
|
|
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(`📁
|
|
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(`✅
|
|
73
|
-
console.log('🔄 Netlify Image Optimization
|
|
74
|
-
console.log('⚡ Sharp
|
|
75
|
-
console.log('📁
|
|
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(`📁
|
|
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
|
-
//
|
|
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(`🔄
|
|
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(`💾
|
|
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('🚀
|
|
158
|
-
console.log('⚙️
|
|
159
|
-
console.log(' WebP:
|
|
160
|
-
console.log(' JPEG:
|
|
161
|
-
console.log(' PNG:
|
|
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
|
-
//
|
|
49
|
+
// Function to optimize through Squoosh CLI
|
|
50
50
|
async function optimizeWithSquoosh() {
|
|
51
|
-
console.log('🚀
|
|
51
|
+
console.log('🚀 Starting optimization through Squoosh CLI...');
|
|
52
52
|
|
|
53
53
|
const images = getAllImages(publicDir);
|
|
54
|
-
console.log(`📁
|
|
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
|
-
//
|
|
81
|
-
console.log('⚡
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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: "
|
|
3
|
-
slug: "
|
|
2
|
+
title: "Рубрики и теги"
|
|
3
|
+
slug: "tags"
|
|
4
4
|
image:
|
|
5
5
|
src: "/defaultRubricImage.webp"
|
|
6
6
|
seo:
|
|
7
|
-
title: "Maugli
|
|
8
|
-
description: "
|
|
9
|
-
keywords: ["
|
|
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
|
|
14
|
-
url: "https://
|
|
15
|
-
description: "
|
|
13
|
+
name: "Рубрики Maugli"
|
|
14
|
+
url: "https://blogru.maugli.cfd/tags"
|
|
15
|
+
description: "Подборки материалов по ключевым темам: ИИ, автоматизация, контент-стратегия, GPT-SEO и дистрибуция. Основано на реальных трендах и проверенных данных."
|
|
16
16
|
publisher:
|
|
17
17
|
"@type": "Organization"
|
|
18
|
-
name: "
|
|
18
|
+
name: "Редакционная платформа Maugli"
|
|
19
19
|
logo:
|
|
20
20
|
"@type": "ImageObject"
|
|
21
|
-
url: "https://
|
|
22
|
-
inLanguage: "
|
|
21
|
+
url: "https://blogru.maugli.cfd/logoblog-icon.svg"
|
|
22
|
+
inLanguage: "ru"
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
Хотя контент **Maugli** — это демо-витрина, все рубрики и статьи собраны из **живых трендов, актуальных тем и проверенных источников**.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Эта страница поможет понять, какие темы и направления мы освещаем, и быстро найти материалы по интересующим вопросам.:
|
|
28
|
+
- Хотите автоматизировать производство контента и маркетинговые процессы;
|
|
29
|
+
- Ищете рабочие способы встроить ИИ в свой пайплайн;
|
|
30
|
+
- Выстраиваете современную контент-стратегию, которая индексируется и людьми, и нейросетями.
|
|
27
31
|
|
|
28
|
-
|
|
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
|
-
|
|
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
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
109
|
+
"allTags": "Tags",
|
|
110
110
|
"rubricAlt": "Catégorie",
|
|
111
111
|
"tagAriaLabel": "Tag : {name} ({count})"
|
|
112
112
|
},
|
package/src/i18n/ja.json
CHANGED
package/src/i18n/ru.json
CHANGED
package/src/i18n/zh.json
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
110
|
-
|
|
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
|
/>
|