core-maugli 1.2.31 → 1.2.33
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/README.md +78 -5
- package/package.json +13 -3
- package/scripts/setup-user-images.js +75 -0
- package/src/components/ArticleMeta.astro +2 -2
- package/src/components/AuthorCard.astro +12 -1
- package/src/components/Avatar.astro +1 -6
- package/src/components/Breadcrumbs.astro +1 -1
- package/src/components/Card.astro +45 -1
- package/src/components/Footer.astro +9 -12
- package/src/components/LanguageSwitcher.astro +2 -16
- package/src/components/NetlifyButton.astro +1 -1
- package/src/components/TagsSection.astro +3 -0
- package/src/i18n/de.json +3 -1
- package/src/i18n/en.json +3 -1
- package/src/i18n/es.json +3 -1
- package/src/i18n/fr.json +3 -1
- package/src/i18n/ja.json +3 -1
- package/src/i18n/pt.json +3 -1
- package/src/i18n/ru.json +3 -1
- package/src/i18n/zh.json +3 -1
- package/src/pages/authors/[id].astro +13 -2
- package/public/img/default/autor_default-1200.webp +0 -0
- package/public/img/default/autor_default-400.webp +0 -0
- package/public/img/default/autor_default-800.webp +0 -0
- package/public/img/default/autor_default.webp +0 -0
- package/public/img/default/blog_default-1200.webp +0 -0
- package/public/img/default/blog_default-400.webp +0 -0
- package/public/img/default/blog_default-800.webp +0 -0
- package/public/img/default/blog_default.webp +0 -0
- package/public/img/default/default-1200.webp +0 -0
- package/public/img/default/default-400.webp +0 -0
- package/public/img/default/default-800.webp +0 -0
- package/public/img/default/default.webp +0 -0
- package/public/img/default/previews/autor_default-1200.webp +0 -0
- package/public/img/default/previews/autor_default-400.webp +0 -0
- package/public/img/default/previews/autor_default-800.webp +0 -0
- package/public/img/default/previews/autor_default.webp +0 -0
- package/public/img/default/previews/blog_default-1200.webp +0 -0
- package/public/img/default/previews/blog_default-400.webp +0 -0
- package/public/img/default/previews/blog_default-800.webp +0 -0
- package/public/img/default/previews/blog_default.webp +0 -0
- package/public/img/default/previews/default-1200.webp +0 -0
- package/public/img/default/previews/default-400.webp +0 -0
- package/public/img/default/previews/default-800.webp +0 -0
- package/public/img/default/previews/default.webp +0 -0
- package/public/img/default/previews/product_default-1200.webp +0 -0
- package/public/img/default/previews/product_default-400.webp +0 -0
- package/public/img/default/previews/product_default-800.webp +0 -0
- package/public/img/default/previews/product_default.webp +0 -0
- package/public/img/default/previews/project_default-1200.webp +0 -0
- package/public/img/default/previews/project_default-400.webp +0 -0
- package/public/img/default/previews/project_default-800.webp +0 -0
- package/public/img/default/previews/project_default.webp +0 -0
- package/public/img/default/previews/rubric_default-1200.webp +0 -0
- package/public/img/default/previews/rubric_default-400.webp +0 -0
- package/public/img/default/previews/rubric_default-800.webp +0 -0
- package/public/img/default/previews/rubric_default.webp +0 -0
- package/public/img/default/previews/test-1200.webp +0 -0
- package/public/img/default/previews/test-400.webp +0 -0
- package/public/img/default/previews/test-800.webp +0 -0
- package/public/img/default/previews/test.webp +0 -0
- package/public/img/default/previews/test2-1200.webp +0 -0
- package/public/img/default/previews/test2-400.webp +0 -0
- package/public/img/default/previews/test2-800.webp +0 -0
- package/public/img/default/previews/test2.webp +0 -0
- package/public/img/default/product_default-1200.webp +0 -0
- package/public/img/default/product_default-400.webp +0 -0
- package/public/img/default/product_default-800.webp +0 -0
- package/public/img/default/product_default.webp +0 -0
- package/public/img/default/project_default-1200.webp +0 -0
- package/public/img/default/project_default-400.webp +0 -0
- package/public/img/default/project_default-800.webp +0 -0
- package/public/img/default/project_default.webp +0 -0
- package/public/img/default/rubric_default-1200.webp +0 -0
- package/public/img/default/rubric_default-400.webp +0 -0
- package/public/img/default/rubric_default-800.webp +0 -0
- package/public/img/default/rubric_default.webp +0 -0
- package/public/img/default/test-1200.webp +0 -0
- package/public/img/default/test-400.webp +0 -0
- package/public/img/default/test-800.webp +0 -0
- package/public/img/default/test.webp +0 -0
- package/public/img/default/test2-1200.webp +0 -0
- package/public/img/default/test2-400.webp +0 -0
- package/public/img/default/test2-800.webp +0 -0
- package/public/img/default/test2.webp +0 -0
- package/public/img/examples/authors/anna-1200.webp +0 -0
- package/public/img/examples/authors/anna-400.webp +0 -0
- package/public/img/examples/authors/anna-800.webp +0 -0
- package/public/img/examples/authors/anna.webp +0 -0
- package/public/img/examples/authors/carlos-1200.webp +0 -0
- package/public/img/examples/authors/carlos-400.webp +0 -0
- package/public/img/examples/authors/carlos-800.webp +0 -0
- package/public/img/examples/authors/carlos.webp +0 -0
- package/public/img/examples/authors/daria-1200.webp +0 -0
- package/public/img/examples/authors/daria-400.webp +0 -0
- package/public/img/examples/authors/daria-800.webp +0 -0
- package/public/img/examples/authors/daria.webp +0 -0
- package/public/img/examples/authors/dmitry-1200.webp +0 -0
- package/public/img/examples/authors/dmitry-400.webp +0 -0
- package/public/img/examples/authors/dmitry-800.webp +0 -0
- package/public/img/examples/authors/dmitry.webp +0 -0
- package/public/img/examples/authors/igor-1200.webp +0 -0
- package/public/img/examples/authors/igor-400.webp +0 -0
- package/public/img/examples/authors/igor-800.webp +0 -0
- package/public/img/examples/authors/igor.webp +0 -0
- package/public/img/examples/authors/john-1200.webp +0 -0
- package/public/img/examples/authors/john-400.webp +0 -0
- package/public/img/examples/authors/john-800.webp +0 -0
- package/public/img/examples/authors/john.webp +0 -0
- package/public/img/examples/blog/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva-1200.webp +0 -0
- package/public/img/examples/blog/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva-400.webp +0 -0
- package/public/img/examples/blog/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva-800.webp +0 -0
- package/public/img/examples/blog/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva.webp +0 -0
- package/public/img/examples/blog/post-2-avtomatizaciya-kontenta-kak-neiroseti-ubivayut-perfekcionizm-v-biznese-1200.webp +0 -0
- package/public/img/examples/blog/post-2-avtomatizaciya-kontenta-kak-neiroseti-ubivayut-perfekcionizm-v-biznese-400.webp +0 -0
- package/public/img/examples/blog/post-2-avtomatizaciya-kontenta-kak-neiroseti-ubivayut-perfekcionizm-v-biznese-800.webp +0 -0
- package/public/img/examples/blog/post-2-avtomatizaciya-kontenta-kak-neiroseti-ubivayut-perfekcionizm-v-biznese.webp +0 -0
- package/public/img/examples/blog/post-3-laik-ne-valyuta-kak-avtomatizaciya-marketinga-spasaet-ot-lozhnyh-metrik-1200.webp +0 -0
- package/public/img/examples/blog/post-3-laik-ne-valyuta-kak-avtomatizaciya-marketinga-spasaet-ot-lozhnyh-metrik-400.webp +0 -0
- package/public/img/examples/blog/post-3-laik-ne-valyuta-kak-avtomatizaciya-marketinga-spasaet-ot-lozhnyh-metrik-800.webp +0 -0
- package/public/img/examples/blog/post-3-laik-ne-valyuta-kak-avtomatizaciya-marketinga-spasaet-ot-lozhnyh-metrik.webp +0 -0
- package/public/img/examples/blog/post-5-5-fatalnyh-oshibok-marketinga-kotorye-ubivayut-startapy-na-starte-1200.webp +0 -0
- package/public/img/examples/blog/post-5-5-fatalnyh-oshibok-marketinga-kotorye-ubivayut-startapy-na-starte-400.webp +0 -0
- package/public/img/examples/blog/post-5-5-fatalnyh-oshibok-marketinga-kotorye-ubivayut-startapy-na-starte-800.webp +0 -0
- package/public/img/examples/blog/post-5-5-fatalnyh-oshibok-marketinga-kotorye-ubivayut-startapy-na-starte.webp +0 -0
- package/public/img/examples/blog/post-6-5-strategii-kontent-marketinga-dlya-startapov-avtomatizaciya-i-revolyuciya-1200.webp +0 -0
- package/public/img/examples/blog/post-6-5-strategii-kontent-marketinga-dlya-startapov-avtomatizaciya-i-revolyuciya-400.webp +0 -0
- package/public/img/examples/blog/post-6-5-strategii-kontent-marketinga-dlya-startapov-avtomatizaciya-i-revolyuciya-800.webp +0 -0
- package/public/img/examples/blog/post-6-5-strategii-kontent-marketinga-dlya-startapov-avtomatizaciya-i-revolyuciya.webp +0 -0
- package/public/img/examples/blog/post-7-viralnyi-kontent-ne-udacha-a-strategiya-avtomatizaciya-marketinga-1200.webp +0 -0
- package/public/img/examples/blog/post-7-viralnyi-kontent-ne-udacha-a-strategiya-avtomatizaciya-marketinga-400.webp +0 -0
- package/public/img/examples/blog/post-7-viralnyi-kontent-ne-udacha-a-strategiya-avtomatizaciya-marketinga-800.webp +0 -0
- package/public/img/examples/blog/post-7-viralnyi-kontent-ne-udacha-a-strategiya-avtomatizaciya-marketinga.webp +0 -0
- package/public/img/examples/blog/post-agent-experience-mcp-biznes-v-epohu-ii-agentov-1200.webp +0 -0
- package/public/img/examples/blog/post-agent-experience-mcp-biznes-v-epohu-ii-agentov-400.webp +0 -0
- package/public/img/examples/blog/post-agent-experience-mcp-biznes-v-epohu-ii-agentov-800.webp +0 -0
- package/public/img/examples/blog/post-agent-experience-mcp-biznes-v-epohu-ii-agentov.webp +0 -0
- package/public/img/examples/blog/post_11-1200.webp +0 -0
- package/public/img/examples/blog/post_11-400.webp +0 -0
- package/public/img/examples/blog/post_11-800.webp +0 -0
- package/public/img/examples/blog/post_11.webp +0 -0
- package/public/img/examples/blog/post_12-1200.webp +0 -0
- package/public/img/examples/blog/post_12-400.webp +0 -0
- package/public/img/examples/blog/post_12-800.webp +0 -0
- package/public/img/examples/blog/post_12.webp +0 -0
- package/public/img/examples/blog/post_1_jsonld_guide-1200.webp +0 -0
- package/public/img/examples/blog/post_1_jsonld_guide-400.webp +0 -0
- package/public/img/examples/blog/post_1_jsonld_guide-800.webp +0 -0
- package/public/img/examples/blog/post_1_jsonld_guide.webp +0 -0
- package/public/img/examples/blog/previews/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva-1200.webp +0 -0
- package/public/img/examples/blog/previews/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva-400.webp +0 -0
- package/public/img/examples/blog/previews/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva-800.webp +0 -0
- package/public/img/examples/blog/previews/post-1-avtomatizaciya-marketinga-kak-ii-osvobozhdaet-predprinimatelei-ot-cifrovogo-rabstva.webp +0 -0
- package/public/img/examples/blog/previews/post-2-avtomatizaciya-kontenta-kak-neiroseti-ubivayut-perfekcionizm-v-biznese-1200.webp +0 -0
- package/public/img/examples/blog/previews/post-2-avtomatizaciya-kontenta-kak-neiroseti-ubivayut-perfekcionizm-v-biznese-400.webp +0 -0
- package/public/img/examples/blog/previews/post-2-avtomatizaciya-kontenta-kak-neiroseti-ubivayut-perfekcionizm-v-biznese-800.webp +0 -0
- package/public/img/examples/blog/previews/post-2-avtomatizaciya-kontenta-kak-neiroseti-ubivayut-perfekcionizm-v-biznese.webp +0 -0
- package/public/img/examples/blog/previews/post-3-laik-ne-valyuta-kak-avtomatizaciya-marketinga-spasaet-ot-lozhnyh-metrik-1200.webp +0 -0
- package/public/img/examples/blog/previews/post-3-laik-ne-valyuta-kak-avtomatizaciya-marketinga-spasaet-ot-lozhnyh-metrik-400.webp +0 -0
- package/public/img/examples/blog/previews/post-3-laik-ne-valyuta-kak-avtomatizaciya-marketinga-spasaet-ot-lozhnyh-metrik-800.webp +0 -0
- package/public/img/examples/blog/previews/post-3-laik-ne-valyuta-kak-avtomatizaciya-marketinga-spasaet-ot-lozhnyh-metrik.webp +0 -0
- package/public/img/examples/blog/previews/post-5-5-fatalnyh-oshibok-marketinga-kotorye-ubivayut-startapy-na-starte-1200.webp +0 -0
- package/public/img/examples/blog/previews/post-5-5-fatalnyh-oshibok-marketinga-kotorye-ubivayut-startapy-na-starte-400.webp +0 -0
- package/public/img/examples/blog/previews/post-5-5-fatalnyh-oshibok-marketinga-kotorye-ubivayut-startapy-na-starte-800.webp +0 -0
- package/public/img/examples/blog/previews/post-5-5-fatalnyh-oshibok-marketinga-kotorye-ubivayut-startapy-na-starte.webp +0 -0
- package/public/img/examples/blog/previews/post-6-5-strategii-kontent-marketinga-dlya-startapov-avtomatizaciya-i-revolyuciya-1200.webp +0 -0
- package/public/img/examples/blog/previews/post-6-5-strategii-kontent-marketinga-dlya-startapov-avtomatizaciya-i-revolyuciya-400.webp +0 -0
- package/public/img/examples/blog/previews/post-6-5-strategii-kontent-marketinga-dlya-startapov-avtomatizaciya-i-revolyuciya-800.webp +0 -0
- package/public/img/examples/blog/previews/post-6-5-strategii-kontent-marketinga-dlya-startapov-avtomatizaciya-i-revolyuciya.webp +0 -0
- package/public/img/examples/blog/previews/post-7-viralnyi-kontent-ne-udacha-a-strategiya-avtomatizaciya-marketinga-1200.webp +0 -0
- package/public/img/examples/blog/previews/post-7-viralnyi-kontent-ne-udacha-a-strategiya-avtomatizaciya-marketinga-400.webp +0 -0
- package/public/img/examples/blog/previews/post-7-viralnyi-kontent-ne-udacha-a-strategiya-avtomatizaciya-marketinga-800.webp +0 -0
- package/public/img/examples/blog/previews/post-7-viralnyi-kontent-ne-udacha-a-strategiya-avtomatizaciya-marketinga.webp +0 -0
- package/public/img/examples/blog/previews/post-agent-experience-mcp-biznes-v-epohu-ii-agentov-1200.webp +0 -0
- package/public/img/examples/blog/previews/post-agent-experience-mcp-biznes-v-epohu-ii-agentov-400.webp +0 -0
- package/public/img/examples/blog/previews/post-agent-experience-mcp-biznes-v-epohu-ii-agentov-800.webp +0 -0
- package/public/img/examples/blog/previews/post-agent-experience-mcp-biznes-v-epohu-ii-agentov.webp +0 -0
- package/public/img/examples/blog/previews/post_11-1200.webp +0 -0
- package/public/img/examples/blog/previews/post_11-400.webp +0 -0
- package/public/img/examples/blog/previews/post_11-800.webp +0 -0
- package/public/img/examples/blog/previews/post_11.webp +0 -0
- package/public/img/examples/blog/previews/post_12-1200.webp +0 -0
- package/public/img/examples/blog/previews/post_12-400.webp +0 -0
- package/public/img/examples/blog/previews/post_12-800.webp +0 -0
- package/public/img/examples/blog/previews/post_12.webp +0 -0
- package/public/img/examples/blog/previews/post_1_jsonld_guide-1200.webp +0 -0
- package/public/img/examples/blog/previews/post_1_jsonld_guide-400.webp +0 -0
- package/public/img/examples/blog/previews/post_1_jsonld_guide-800.webp +0 -0
- package/public/img/examples/blog/previews/post_1_jsonld_guide.webp +0 -0
- package/public/img/examples/blog/previews/test-post-1200.webp +0 -0
- package/public/img/examples/blog/previews/test-post-400.webp +0 -0
- package/public/img/examples/blog/previews/test-post-800.webp +0 -0
- package/public/img/examples/blog/previews/test-post.webp +0 -0
- package/public/img/examples/blog/previews/tr-post-1.webp +0 -0
- package/public/img/examples/blog/test-post-1200.webp +0 -0
- package/public/img/examples/blog/test-post-400.webp +0 -0
- package/public/img/examples/blog/test-post-800.webp +0 -0
- package/public/img/examples/blog/test-post.webp +0 -0
- package/public/img/examples/blog/tr-post-1.webp +0 -0
- package/public/img/examples/products/previews/product_1-1200.webp +0 -0
- package/public/img/examples/products/previews/product_1-400.webp +0 -0
- package/public/img/examples/products/previews/product_1-800.webp +0 -0
- package/public/img/examples/products/previews/product_1.webp +0 -0
- package/public/img/examples/products/previews/product_2-1200.webp +0 -0
- package/public/img/examples/products/previews/product_2-400.webp +0 -0
- package/public/img/examples/products/previews/product_2-800.webp +0 -0
- package/public/img/examples/products/previews/product_2.webp +0 -0
- package/public/img/examples/products/product_1-1200.webp +0 -0
- package/public/img/examples/products/product_1-400.webp +0 -0
- package/public/img/examples/products/product_1-800.webp +0 -0
- package/public/img/examples/products/product_1.webp +0 -0
- package/public/img/examples/products/product_2-1200.webp +0 -0
- package/public/img/examples/products/product_2-400.webp +0 -0
- package/public/img/examples/products/product_2-800.webp +0 -0
- package/public/img/examples/products/product_2.webp +0 -0
- package/public/img/examples/projects/previews/project_1-1200.webp +0 -0
- package/public/img/examples/projects/previews/project_1-400.webp +0 -0
- package/public/img/examples/projects/previews/project_1-800.webp +0 -0
- package/public/img/examples/projects/previews/project_1.webp +0 -0
- package/public/img/examples/projects/previews/project_2-1200.webp +0 -0
- package/public/img/examples/projects/previews/project_2-400.webp +0 -0
- package/public/img/examples/projects/previews/project_2-800.webp +0 -0
- package/public/img/examples/projects/previews/project_2.webp +0 -0
- package/public/img/examples/projects/project_1-1200.webp +0 -0
- package/public/img/examples/projects/project_1-400.webp +0 -0
- package/public/img/examples/projects/project_1-800.webp +0 -0
- package/public/img/examples/projects/project_1.webp +0 -0
- package/public/img/examples/projects/project_2-1200.webp +0 -0
- package/public/img/examples/projects/project_2-400.webp +0 -0
- package/public/img/examples/projects/project_2-800.webp +0 -0
- package/public/img/examples/projects/project_2.webp +0 -0
- package/public/img/page-images/blog_default-1200.webp +0 -0
- package/public/img/page-images/blog_default-400.webp +0 -0
- package/public/img/page-images/blog_default-800.webp +0 -0
- package/public/img/page-images/blog_default.webp +0 -0
package/README.md
CHANGED
@@ -87,18 +87,91 @@ Your blog will be available at `http://localhost:4321/`
|
|
87
87
|
npm run build
|
88
88
|
```
|
89
89
|
|
90
|
+
## 🖼️ Image Management
|
91
|
+
|
92
|
+
Maugli Blog uses a **smart image management system** that separates user content from system assets:
|
93
|
+
|
94
|
+
### Your Images Are Protected ✅
|
95
|
+
|
96
|
+
During npm updates, **your images are preserved**:
|
97
|
+
- `public/img/blog/` - Your blog post images
|
98
|
+
- `public/img/authors/` - Your author photos
|
99
|
+
- `public/img/uploads/` - Your uploaded content
|
100
|
+
- `public/img/products/` - Your product images
|
101
|
+
- `public/img/projects/` - Your project images
|
102
|
+
|
103
|
+
### System Assets Are Updated 🔄
|
104
|
+
|
105
|
+
These are managed automatically by npm updates:
|
106
|
+
- `public/favicon.svg`, logos, icons
|
107
|
+
- `public/flags/` - Country flags
|
108
|
+
- `public/img/default/` - Default fallback images
|
109
|
+
|
110
|
+
### Automatic Image Optimization
|
111
|
+
|
112
|
+
The system automatically generates responsive versions:
|
113
|
+
```bash
|
114
|
+
# From: my-post.webp
|
115
|
+
# Creates: my-post-400.webp (mobile)
|
116
|
+
# my-post-800.webp (tablet)
|
117
|
+
# my-post-1200.webp (desktop)
|
118
|
+
# previews/my-post.webp (thumbnail)
|
119
|
+
```
|
120
|
+
|
121
|
+
**Best practices:**
|
122
|
+
- Use WebP format for better performance
|
123
|
+
- Blog images: max 1200px width
|
124
|
+
- Author avatars: 400x400px recommended
|
125
|
+
|
126
|
+
See [detailed image management guide](docs/USER-IMAGES.md) for more information.
|
127
|
+
|
90
128
|
## Component Updates & Customization
|
91
129
|
|
92
|
-
|
130
|
+
⸻
|
131
|
+
|
132
|
+
**Important**: Maugli Blog is designed for centralized component updates. All core components (`src/components/`, `src/layouts/`, `src/pages/`, etc.) are automatically updated to the latest version when you run:
|
133
|
+
|
134
|
+
```bash
|
135
|
+
npm install --save core-maugli@latest
|
136
|
+
```
|
137
|
+
|
138
|
+
This ensures that you always receive the latest:
|
139
|
+
- **Features**
|
140
|
+
- **Bug fixes**
|
141
|
+
- **Performance improvements**
|
142
|
+
- **Accessibility enhancements**
|
143
|
+
- **Lighthouse-validated optimizations**
|
144
|
+
|
145
|
+
### Why Centralized Updates?
|
146
|
+
|
147
|
+
**1. Automation First**
|
148
|
+
Manual component maintenance is time-consuming and error-prone. Centralized updates free you from technical debt, allowing you to focus on content creation and business growth instead of code maintenance.
|
149
|
+
|
150
|
+
**2. Lighthouse & Performance Excellence**
|
151
|
+
All Maugli components are crafted to comply with strict Lighthouse, Web Vitals, and AI-indexability guidelines. Every component update includes:
|
152
|
+
- **Mobile UX optimization** (48px touch targets, responsive design)
|
153
|
+
- **Performance optimization** (proper image loading, minimal layout shift)
|
154
|
+
- **SEO compliance** (structured data, semantic HTML, accessibility)
|
155
|
+
- **Core Web Vitals** (LCP, FID, CLS optimization)
|
156
|
+
|
157
|
+
Manual changes may negatively affect your site's score in:
|
158
|
+
- **SEO**
|
159
|
+
- **Performance**
|
160
|
+
- **Accessibility**
|
161
|
+
- **Best Practices**
|
162
|
+
|
163
|
+
⚠️ **We do not recommend editing core components manually.** If you do, re-test your site with Lighthouse and search engine validators after every change.
|
164
|
+
|
165
|
+
⸻
|
93
166
|
|
94
167
|
This centralized update approach **does not affect**:
|
95
168
|
|
96
169
|
- Your content (`src/content/`)
|
97
|
-
- Your
|
98
|
-
- Your
|
99
|
-
- Your project
|
170
|
+
- Your configuration (`src/config/maugli.config.ts`)
|
171
|
+
- Your styles (`src/styles/global.css`) — preserved if customized
|
172
|
+
- Your project files (`package.json`, `astro.config.mjs`, etc.)
|
100
173
|
|
101
|
-
|
174
|
+
⸻
|
102
175
|
|
103
176
|
`npm run build` runs [`scripts/verify-assets.js`](scripts/verify-assets.js)
|
104
177
|
before the Astro build. This script checks the SHA-256 hashes of the
|
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.
|
5
|
+
"version": "1.2.33",
|
6
6
|
"license": "GPL-3.0-or-later OR Commercial",
|
7
7
|
"repository": {
|
8
8
|
"type": "git",
|
@@ -31,7 +31,7 @@
|
|
31
31
|
"upgrade": "node scripts/upgrade-config.js",
|
32
32
|
"update-components": "node scripts/update-components.js",
|
33
33
|
"backup-update": "node scripts/update-with-backup.js",
|
34
|
-
"postinstall": "node scripts/upgrade-config.js",
|
34
|
+
"postinstall": "node scripts/upgrade-config.js && node scripts/setup-user-images.js",
|
35
35
|
"generate-previews": "node scripts/generate-previews.js"
|
36
36
|
},
|
37
37
|
"dependencies": {
|
@@ -63,7 +63,17 @@
|
|
63
63
|
},
|
64
64
|
"files": [
|
65
65
|
"src",
|
66
|
-
"public",
|
66
|
+
"public/favicon.svg",
|
67
|
+
"public/icon-192.png",
|
68
|
+
"public/icon-512.png",
|
69
|
+
"public/manifest.webmanifest",
|
70
|
+
"public/logo-icon.svg",
|
71
|
+
"public/logoblog-icon.svg",
|
72
|
+
"public/maugli_for_animation.svg",
|
73
|
+
"public/mauglilabel.svg",
|
74
|
+
"public/footerlabel.svg",
|
75
|
+
"public/flags/",
|
76
|
+
"public/blackbox*.webp",
|
67
77
|
"scripts",
|
68
78
|
"bin"
|
69
79
|
],
|
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Image Management Script for Maugli Blog
|
5
|
+
* Handles user images separately from core system assets
|
6
|
+
*/
|
7
|
+
|
8
|
+
import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync } from 'fs';
|
9
|
+
import { join, dirname } from 'path';
|
10
|
+
import { fileURLToPath } from 'url';
|
11
|
+
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
13
|
+
const __dirname = dirname(__filename);
|
14
|
+
|
15
|
+
const PUBLIC_DIR = join(__dirname, '..', 'public');
|
16
|
+
const IMG_DIR = join(PUBLIC_DIR, 'img');
|
17
|
+
|
18
|
+
// Ensure user image directories exist
|
19
|
+
function ensureUserDirectories() {
|
20
|
+
const userDirs = [
|
21
|
+
join(IMG_DIR, 'uploads'),
|
22
|
+
join(IMG_DIR, 'blog'),
|
23
|
+
join(IMG_DIR, 'authors'),
|
24
|
+
join(IMG_DIR, 'products'),
|
25
|
+
join(IMG_DIR, 'projects'),
|
26
|
+
join(IMG_DIR, 'previews'),
|
27
|
+
];
|
28
|
+
|
29
|
+
userDirs.forEach(dir => {
|
30
|
+
if (!existsSync(dir)) {
|
31
|
+
mkdirSync(dir, { recursive: true });
|
32
|
+
console.log(`✅ Created directory: ${dir}`);
|
33
|
+
}
|
34
|
+
});
|
35
|
+
}
|
36
|
+
|
37
|
+
// Copy default images if they don't exist
|
38
|
+
function ensureDefaultImages() {
|
39
|
+
const defaultDir = join(IMG_DIR, 'default');
|
40
|
+
if (!existsSync(defaultDir)) {
|
41
|
+
mkdirSync(defaultDir, { recursive: true });
|
42
|
+
console.log(`✅ Created default images directory`);
|
43
|
+
}
|
44
|
+
|
45
|
+
// Create placeholder default images if needed
|
46
|
+
const defaultImages = [
|
47
|
+
'autor_default.webp',
|
48
|
+
'blog_default.webp',
|
49
|
+
'product_default.webp',
|
50
|
+
'project_default.webp'
|
51
|
+
];
|
52
|
+
|
53
|
+
// Note: In real implementation, we'd copy actual default images
|
54
|
+
console.log(`📁 Default images directory ready at: ${defaultDir}`);
|
55
|
+
}
|
56
|
+
|
57
|
+
// Main setup function
|
58
|
+
function setupUserImages() {
|
59
|
+
console.log('🖼️ Setting up user image directories...');
|
60
|
+
|
61
|
+
ensureUserDirectories();
|
62
|
+
ensureDefaultImages();
|
63
|
+
|
64
|
+
console.log('✅ User image setup complete!');
|
65
|
+
console.log('');
|
66
|
+
console.log('📝 User images are preserved during npm updates');
|
67
|
+
console.log('🔧 Core system assets are managed by the npm package');
|
68
|
+
}
|
69
|
+
|
70
|
+
// Run if called directly
|
71
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
72
|
+
setupUserImages();
|
73
|
+
}
|
74
|
+
|
75
|
+
export { setupUserImages };
|
@@ -72,12 +72,12 @@ let authorImg = authorData.data.avatar || '/img/default/autor_default.webp';
|
|
72
72
|
data-astro-reload
|
73
73
|
style="text-decoration: none; color: inherit; z-index: 10; position: relative;"
|
74
74
|
>
|
75
|
-
<img src={authorImg} alt={
|
75
|
+
<img src={authorImg} alt={dict.ui?.authorAvatar || 'Author avatar'} class="w-8 h-8 rounded-full" width="32" height="32" decoding="async" />
|
76
76
|
<span class="font-medium hover:text-[var(--brand-color)] transition-colors duration-200">{authorName}</span>
|
77
77
|
</a>
|
78
78
|
) : (
|
79
79
|
<>
|
80
|
-
<img src={authorImg} alt={
|
80
|
+
<img src={authorImg} alt={dict.ui?.authorAvatar || 'Author avatar'} class="w-8 h-8 rounded-full" width="32" height="32" decoding="async" />
|
81
81
|
<span class="font-medium">{authorName}</span>
|
82
82
|
</>
|
83
83
|
)
|
@@ -1,11 +1,22 @@
|
|
1
1
|
---
|
2
2
|
import { maugliConfig } from '../config/maugli.config';
|
3
|
+
import { LANGUAGES } from '../i18n/languages';
|
3
4
|
import { getFilteredCollection } from '../utils/content-loader';
|
4
5
|
import { getPostsByAuthor } from '../utils/data-utils';
|
5
6
|
import AuthorLinksGroup from './AuthorLinksGroup.astro';
|
6
7
|
import Avatar from './Avatar.astro';
|
7
8
|
import CountBadge from './CountBadge.astro';
|
8
9
|
|
10
|
+
// Универсальный импорт словарей по доступным языкам
|
11
|
+
const dicts: Record<string, any> = {};
|
12
|
+
for (const lang of LANGUAGES) {
|
13
|
+
try {
|
14
|
+
dicts[lang.code] = await import(`../i18n/${lang.code}.json`).then((m) => m.default);
|
15
|
+
} catch {}
|
16
|
+
}
|
17
|
+
const lang = maugliConfig.defaultLang || 'en';
|
18
|
+
const dict = dicts[lang] || dicts['en'] || {};
|
19
|
+
|
9
20
|
export interface Props {
|
10
21
|
author: {
|
11
22
|
name: string;
|
@@ -58,7 +69,7 @@ const postCount = getPostsByAuthor(posts, slug).length;
|
|
58
69
|
<!-- Аватар автора слева -->
|
59
70
|
<Avatar
|
60
71
|
src={avatar || maugliConfig.defaultAuthorImage || '/img/default/autor_default.webp'}
|
61
|
-
alt={
|
72
|
+
alt={dict.ui?.authorAvatar || 'Author avatar'}
|
62
73
|
size="clamp(70px, 15vw, 100px)"
|
63
74
|
class="no-border"
|
64
75
|
/>
|
@@ -13,12 +13,7 @@ const avatarSize = typeof size === 'number' ? `${size}px` : size;
|
|
13
13
|
---
|
14
14
|
|
15
15
|
<div class:list={['avatar-container', className]} style={`width: ${avatarSize}; height: ${avatarSize};`}>
|
16
|
-
<img
|
17
|
-
src={src || '/img/default/autor_default.webp'}
|
18
|
-
alt={alt}
|
19
|
-
decoding="async"
|
20
|
-
class="w-full h-full object-cover"
|
21
|
-
/>
|
16
|
+
<img src={src || '/img/default/autor_default.webp'} alt={alt} decoding="async" class="w-full h-full object-cover" />
|
22
17
|
</div>
|
23
18
|
|
24
19
|
<style>
|
@@ -62,7 +62,7 @@ const isTagPage = currentUrl.startsWith('/tags/') && pathParts.length === 1;
|
|
62
62
|
idx === 0 ? (
|
63
63
|
<a href={crumb.href} class="flex-shrink-0" target="_blank" rel="noopener noreferrer">
|
64
64
|
<picture>
|
65
|
-
<img src={crumb.icon} alt="Maugli" class="w-9 h-9" decoding="async" />
|
65
|
+
<img src={crumb.icon} alt="Maugli" class="w-9 h-9" width="36" height="36" decoding="async" />
|
66
66
|
</picture>
|
67
67
|
</a>
|
68
68
|
) : (
|
@@ -1,6 +1,12 @@
|
|
1
1
|
---
|
2
2
|
import { maugliConfig } from '../config/maugli.config';
|
3
3
|
import FormattedDate from './FormattedDate.astro';
|
4
|
+
import fs from 'fs';
|
5
|
+
import path from 'path';
|
6
|
+
import { fileURLToPath } from 'url';
|
7
|
+
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
9
|
+
const projectRoot = path.resolve(path.dirname(__filename), '../..');
|
4
10
|
|
5
11
|
type Props = {
|
6
12
|
href: string;
|
@@ -36,6 +42,42 @@ const cardImageAlt = seo?.image?.alt || image?.alt || title || 'Изображе
|
|
36
42
|
|
37
43
|
// Определяем контент для отображения
|
38
44
|
const content = excerpt || description;
|
45
|
+
|
46
|
+
// Функция для создания responsive изображений
|
47
|
+
function getResponsiveImages(imagePath: string) {
|
48
|
+
const basePath = imagePath.replace(/\.(webp|jpg|jpeg|png)$/i, '');
|
49
|
+
const extension = imagePath.match(/\.(webp|jpg|jpeg|png)$/i)?.[0] || '.webp';
|
50
|
+
|
51
|
+
const variants = [
|
52
|
+
{ suffix: '-400', width: '400w' },
|
53
|
+
{ suffix: '-800', width: '800w' },
|
54
|
+
{ suffix: '-1200', width: '1200w' }
|
55
|
+
];
|
56
|
+
|
57
|
+
const srcsetItems = [];
|
58
|
+
|
59
|
+
// Добавляем адаптивные версии, если они существуют
|
60
|
+
for (const variant of variants) {
|
61
|
+
const variantPath = `${basePath}${variant.suffix}${extension}`;
|
62
|
+
const filePath = path.join(projectRoot, 'public', variantPath.replace(/^\//, ''));
|
63
|
+
|
64
|
+
if (fs.existsSync(filePath)) {
|
65
|
+
srcsetItems.push(`${variantPath} ${variant.width}`);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
// Всегда добавляем оригинальное изображение
|
70
|
+
srcsetItems.push(`${imagePath} 1200w`);
|
71
|
+
|
72
|
+
return {
|
73
|
+
src: imagePath,
|
74
|
+
srcset: srcsetItems.join(', '),
|
75
|
+
sizes: '(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 400px'
|
76
|
+
};
|
77
|
+
}
|
78
|
+
|
79
|
+
// Генерируем responsive изображения для карточки
|
80
|
+
const imageData = getResponsiveImages(baseImage);
|
39
81
|
---
|
40
82
|
|
41
83
|
<article
|
@@ -49,11 +91,13 @@ const content = excerpt || description;
|
|
49
91
|
<!-- Изображение -->
|
50
92
|
<div class="w-full aspect-[1200/630] bg-[var(--bg-muted)] overflow-hidden relative">
|
51
93
|
<img
|
52
|
-
src={
|
94
|
+
src={imageData.src}
|
53
95
|
alt={cardImageAlt}
|
54
96
|
loading="eager"
|
55
97
|
width="1200"
|
56
98
|
height="630"
|
99
|
+
srcset={imageData.srcset}
|
100
|
+
sizes={imageData.sizes}
|
57
101
|
class="w-full h-full object-cover rounded-custom transition-transform duration-300 group-hover:scale-105"
|
58
102
|
/>
|
59
103
|
|
@@ -55,7 +55,7 @@ const copyrightYears = yearStart === yearEnd ? `${yearStart}` : `${yearStart}–
|
|
55
55
|
<div class="flex flex-wrap gap-x-6 gap-y-1 items-center">
|
56
56
|
{lang === 'ru' ? (
|
57
57
|
<a
|
58
|
-
class="footer-link flex items-center text-muted mr-2"
|
58
|
+
class="footer-link flex items-center text-muted mr-2 min-w-12 min-h-12 sm:min-w-0 sm:min-h-0"
|
59
59
|
href="/rss.xml"
|
60
60
|
target="_blank"
|
61
61
|
rel="noopener"
|
@@ -64,7 +64,13 @@ const copyrightYears = yearStart === yearEnd ? `${yearStart}` : `${yearStart}–
|
|
64
64
|
RSS
|
65
65
|
</a>
|
66
66
|
) : (
|
67
|
-
<a
|
67
|
+
<a
|
68
|
+
class="footer-link flex items-center mr-2 min-w-12 min-h-12 sm:min-w-0 sm:min-h-0"
|
69
|
+
href="/rss.xml"
|
70
|
+
aria-label="RSS"
|
71
|
+
target="_blank"
|
72
|
+
rel="noopener"
|
73
|
+
>
|
68
74
|
<svg
|
69
75
|
width="20"
|
70
76
|
height="20"
|
@@ -83,7 +89,7 @@ const copyrightYears = yearStart === yearEnd ? `${yearStart}` : `${yearStart}–
|
|
83
89
|
</a>
|
84
90
|
)}
|
85
91
|
{navLinks.map((link) => (
|
86
|
-
<a class="footer-link font-serif text-heading" href={link.href}>
|
92
|
+
<a class="footer-link font-serif text-heading min-w-12 min-h-12 sm:min-w-0 sm:min-h-0 flex items-center" href={link.href}>
|
87
93
|
{link.label}
|
88
94
|
</a>
|
89
95
|
))}
|
@@ -112,15 +118,6 @@ const copyrightYears = yearStart === yearEnd ? `${yearStart}` : `${yearStart}–
|
|
112
118
|
<a href="https://www.npmjs.com/package/core-maugli" target="_blank" rel="noopener" aria-label="Core Maugli на NPM">
|
113
119
|
<img src="/footerlabel.svg" alt="Maugli Label" style="height:32px;width:auto;" loading="lazy" decoding="async" width="184" height="68" />
|
114
120
|
</a>
|
115
|
-
<a
|
116
|
-
href="https://www.npmjs.com/package/core-maugli"
|
117
|
-
target="_blank"
|
118
|
-
rel="noopener noreferrer"
|
119
|
-
class="footer-link text-xs opacity-60 hover:opacity-100"
|
120
|
-
style="color:var(--text-muted);font-family:var(--font-sans);margin-left:8px;"
|
121
|
-
>
|
122
|
-
npm package
|
123
|
-
</a>
|
124
121
|
{
|
125
122
|
Object.values(maugliConfig.links || {}).some(Boolean) && (
|
126
123
|
<div class="flex flex-row items-center gap-2 ml-4">
|
@@ -25,14 +25,7 @@ const current = availableLanguages.find((l) => l.code === currentLang) || availa
|
|
25
25
|
style="min-width:28px; min-height:28px; max-width:28px; max-height:28px;"
|
26
26
|
>
|
27
27
|
{current && (
|
28
|
-
<img
|
29
|
-
src={current.icon}
|
30
|
-
alt={current.code.toUpperCase()}
|
31
|
-
width="28"
|
32
|
-
height="28"
|
33
|
-
style="pointer-events:none;"
|
34
|
-
decoding="async"
|
35
|
-
/>
|
28
|
+
<img src={current.icon} alt={current.code.toUpperCase()} width="28" height="28" style="pointer-events:none;" decoding="async" />
|
36
29
|
)}
|
37
30
|
</span>
|
38
31
|
<svg class="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
|
@@ -66,14 +59,7 @@ const current = availableLanguages.find((l) => l.code === currentLang) || availa
|
|
66
59
|
class="flag-box flex items-center justify-center w-7 h-7 rounded-[var(--radius-main)] shadow-[var(--shadow-main)]"
|
67
60
|
style="min-width:28px; min-height:28px; max-width:28px; max-height:28px;"
|
68
61
|
>
|
69
|
-
<img
|
70
|
-
src={lang.icon}
|
71
|
-
alt={lang.code.toUpperCase()}
|
72
|
-
width="28"
|
73
|
-
height="28"
|
74
|
-
style="pointer-events:none;"
|
75
|
-
decoding="async"
|
76
|
-
/>
|
62
|
+
<img src={lang.icon} alt={lang.code.toUpperCase()} width="28" height="28" style="pointer-events:none;" decoding="async" />
|
77
63
|
</span>
|
78
64
|
<span class="text-heading">{lang.label}</span>
|
79
65
|
</a>
|
@@ -16,7 +16,7 @@ const netlifyUrl = `https://app.netlify.com/start/deploy?repository=${repository
|
|
16
16
|
{
|
17
17
|
netlifyEnabled && (
|
18
18
|
<a href={netlifyUrl} target="_blank" rel="noopener noreferrer" class={className}>
|
19
|
-
<img src="https://www.netlify.com/img/deploy/button.svg" alt="Deploy to Netlify" loading="lazy" decoding="async" />
|
19
|
+
<img src="https://www.netlify.com/img/deploy/button.svg" alt="Deploy to Netlify" width="114" height="32" loading="lazy" decoding="async" />
|
20
20
|
</a>
|
21
21
|
)
|
22
22
|
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
---
|
2
2
|
import { maugliConfig } from '../config/maugli.config';
|
3
3
|
import { LANGUAGES } from '../i18n/languages';
|
4
|
+
import OptimizedImage from './OptimizedImage.astro';
|
4
5
|
export interface Props {
|
5
6
|
tags: Array<{
|
6
7
|
id: string;
|
@@ -43,6 +44,8 @@ const rubricImageFinal = rubricImage && rubricImage.length > 0 ? rubricImage : m
|
|
43
44
|
alt={rubricName || tagsSection.rubricAlt}
|
44
45
|
class="w-20 h-20 sm:w-40 sm:h-40 object-cover rounded-[16px_48px_16px_48px] border border-[var(--border-main)]"
|
45
46
|
style="flex-shrink:0;"
|
47
|
+
width="160"
|
48
|
+
height="160"
|
46
49
|
decoding="async"
|
47
50
|
/>
|
48
51
|
<div class="flex flex-col items-center sm:items-start text-center sm:text-left">
|
package/src/i18n/de.json
CHANGED
package/src/i18n/en.json
CHANGED
package/src/i18n/es.json
CHANGED
package/src/i18n/fr.json
CHANGED
package/src/i18n/ja.json
CHANGED
package/src/i18n/pt.json
CHANGED
package/src/i18n/ru.json
CHANGED
package/src/i18n/zh.json
CHANGED
@@ -5,6 +5,7 @@ import PostPreview from '../../components/PostPreview.astro';
|
|
5
5
|
import Subscribe from '../../components/Subscribe.astro';
|
6
6
|
import TagsSection from '../../components/TagsSection.astro';
|
7
7
|
import { maugliConfig } from '../../config/maugli.config';
|
8
|
+
import { LANGUAGES } from '../../i18n/languages';
|
8
9
|
import BlueskyIcon from '../../icons/socials/BlueskyIcon.astro';
|
9
10
|
import EmailIcon from '../../icons/socials/EmailIcon.astro';
|
10
11
|
import LinkedinIcon from '../../icons/socials/LinkedinIcon.astro';
|
@@ -33,6 +34,16 @@ const { name, position, description, avatar: rawAvatar, socials, seo } = author.
|
|
33
34
|
const avatar = rawAvatar ? rawAvatar.replace('/img/examples/authors/', '/img/authors/') : undefined;
|
34
35
|
const { Content } = await render(author);
|
35
36
|
|
37
|
+
// Универсальный импорт словарей по доступным языкам
|
38
|
+
const dicts: Record<string, any> = {};
|
39
|
+
for (const lang of LANGUAGES) {
|
40
|
+
try {
|
41
|
+
dicts[lang.code] = await import(`../../i18n/${lang.code}.json`).then((m) => m.default);
|
42
|
+
} catch {}
|
43
|
+
}
|
44
|
+
const lang = maugliConfig.defaultLang || 'en';
|
45
|
+
const dict = dicts[lang] || dicts['en'] || {};
|
46
|
+
|
36
47
|
// Получаем все статьи блога
|
37
48
|
let allPosts = await getFilteredCollection('blog');
|
38
49
|
|
@@ -80,7 +91,7 @@ const pageTitle = seo?.title || `${name} - ${position} | Maugli Content Farm`;
|
|
80
91
|
const pageDescription = seo?.description || description;
|
81
92
|
---
|
82
93
|
|
83
|
-
<BaseLayout title={pageTitle} description={pageDescription} image={{ src: avatar || '/img/default/autor_default.webp', alt:
|
94
|
+
<BaseLayout title={pageTitle} description={pageDescription} image={{ src: avatar || '/img/default/autor_default.webp', alt: dict.ui?.authorAvatar || 'Author avatar' }} showHeader={false} fullWidth={true}>
|
84
95
|
<div class="max-w-[1280px] mx-auto">
|
85
96
|
<!-- Хлебные крошки -->
|
86
97
|
<Breadcrumbs />
|
@@ -91,7 +102,7 @@ const pageDescription = seo?.description || description;
|
|
91
102
|
<div class="flex flex-col sm:flex-row gap-6 sm:gap-8 items-start">
|
92
103
|
<!-- Аватар -->
|
93
104
|
<div class="w-32 h-32 sm:w-40 sm:h-40 bg-[var(--bg-main)] rounded-full overflow-hidden flex-shrink-0">
|
94
|
-
<img src={avatar || '/img/default/autor_default.webp'} alt={
|
105
|
+
<img src={avatar || '/img/default/autor_default.webp'} alt={dict.ui?.authorAvatar || 'Author avatar'} class="w-full h-full object-cover" width="160" height="160" decoding="async" />
|
95
106
|
</div>
|
96
107
|
|
97
108
|
<!-- Информация -->
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|