core-maugli 1.2.4 → 1.2.5

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.
Files changed (66) hide show
  1. package/package.json +1 -1
  2. package/public/img/default/autor_default.webp +0 -0
  3. package/public/img/default/blog_default.webp +0 -0
  4. package/public/img/default/default.webp +0 -0
  5. package/public/img/default/product_default.webp +0 -0
  6. package/public/img/default/project_default.webp +0 -0
  7. package/public/img/default/rubric_default.webp +0 -0
  8. package/public/img/default/test.webp +0 -0
  9. package/public/img/default/test2.webp +0 -0
  10. package/scripts/update-components.js +14 -5
  11. package/src/i18n/de.json +126 -0
  12. package/src/i18n/en.json +126 -0
  13. package/src/i18n/es.json +126 -0
  14. package/src/i18n/fr.json +126 -0
  15. package/src/i18n/index.ts +10 -0
  16. package/src/i18n/ja.json +126 -0
  17. package/src/i18n/languages.ts +23 -0
  18. package/src/i18n/pt.json +126 -0
  19. package/src/i18n/ru.json +123 -0
  20. package/src/i18n/zh.json +126 -0
  21. package/src/icons/ArrowLeft.astro +13 -0
  22. package/src/icons/ArrowRight.astro +13 -0
  23. package/src/icons/flags/brazil.svg +14 -0
  24. package/src/icons/flags/china.svg +15 -0
  25. package/src/icons/flags/france.svg +12 -0
  26. package/src/icons/flags/germany.svg +12 -0
  27. package/src/icons/flags/japan.svg +11 -0
  28. package/src/icons/flags/russia.svg +12 -0
  29. package/src/icons/flags/spain.svg +12 -0
  30. package/src/icons/flags/united arab emirates.svg +13 -0
  31. package/src/icons/flags/united states.svg +15 -0
  32. package/src/icons/socials/BlueskyIcon.astro +9 -0
  33. package/src/icons/socials/EmailIcon.astro +8 -0
  34. package/src/icons/socials/LinkedinIcon.astro +9 -0
  35. package/src/icons/socials/MastodonIcon.astro +9 -0
  36. package/src/icons/socials/MediumIcon.astro +9 -0
  37. package/src/icons/socials/RedditIcon.astro +11 -0
  38. package/src/icons/socials/TelegramIcon.astro +11 -0
  39. package/src/icons/socials/TwitterIcon.astro +9 -0
  40. package/src/layouts/BaseLayout.astro +59 -0
  41. package/src/pages/404.astro +24 -0
  42. package/src/pages/[...id].astro +50 -0
  43. package/src/pages/about.astro +0 -0
  44. package/src/pages/authors/[...page].astro +105 -0
  45. package/src/pages/authors/[id].astro +175 -0
  46. package/src/pages/blog/[...page].astro +59 -0
  47. package/src/pages/blog/[id].astro +175 -0
  48. package/src/pages/index.astro +90 -0
  49. package/src/pages/products/[...page].astro +50 -0
  50. package/src/pages/products/[id].astro +221 -0
  51. package/src/pages/projects/[...page].astro +74 -0
  52. package/src/pages/projects/[id].astro +165 -0
  53. package/src/pages/projects/tags/[id]/[...page].astro +58 -0
  54. package/src/pages/rss.xml.js +5 -0
  55. package/src/pages/tags/[id]/[...page].astro +110 -0
  56. package/src/pages/tags/index.astro +124 -0
  57. package/src/scripts/infoCardFadeIn.js +22 -0
  58. package/src/styles/global.css +273 -0
  59. package/src/utils/common-utils.ts +0 -0
  60. package/src/utils/content-loader.ts +14 -0
  61. package/src/utils/data-utils.ts +49 -0
  62. package/src/utils/featuredManager.ts +118 -0
  63. package/src/utils/posts.ts +43 -0
  64. package/src/utils/reading-time.ts +28 -0
  65. package/src/utils/remark-slugify.js +8 -0
  66. package/src/utils/rss.ts +23 -0
@@ -0,0 +1,13 @@
1
+ <svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g clip-path="url(#clip0_105_41)">
3
+ <path d="M256 511.999C397.385 511.999 512 397.384 512 255.999C512 114.614 397.385 -0.000976562 256 -0.000976562C114.615 -0.000976562 0 114.614 0 255.999C0 397.384 114.615 511.999 256 511.999Z" fill="#F0F0F0"/>
4
+ <path d="M144.695 345.042L166.956 496.078C194.688 506.369 224.685 511.999 255.999 511.999C366.069 511.999 459.905 442.527 496.075 345.042H144.695Z" fill="black"/>
5
+ <path d="M144.695 166.956L166.956 15.92C194.688 5.62902 224.685 -0.000976562 255.999 -0.000976562C366.069 -0.000976562 459.905 69.471 496.075 166.956H144.695Z" fill="#6DA544"/>
6
+ <path d="M0 255.999C0 366.07 69.473 459.905 166.957 496.076V15.9221C69.473 52.0931 0 145.928 0 255.999Z" fill="#A2001D"/>
7
+ </g>
8
+ <defs>
9
+ <clipPath id="clip0_105_41">
10
+ <rect width="512" height="512" fill="white" transform="translate(0 -0.000976562)"/>
11
+ </clipPath>
12
+ </defs>
13
+ </svg>
@@ -0,0 +1,15 @@
1
+ <svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g clip-path="url(#clip0_3719_7206)">
3
+ <path d="M256 512C397.385 512 512 397.385 512 256C512 114.615 397.385 0 256 0C114.615 0 0 114.615 0 256C0 397.385 114.615 512 256 512Z" fill="#F0F0F0"/>
4
+ <path d="M244.869 256H511.999C511.999 232.894 508.919 210.51 503.18 189.217H244.869V256Z" fill="#D80027"/>
5
+ <path d="M244.869 122.435H474.425C458.754 96.8633 438.717 74.2603 415.355 55.6523H244.869V122.435Z" fill="#D80027"/>
6
+ <path d="M255.999 512C316.248 512 371.625 491.176 415.355 456.348H96.6426C140.373 491.176 195.75 512 255.999 512Z" fill="#D80027"/>
7
+ <path d="M37.5734 389.566H474.425C487.006 369.037 496.763 346.597 503.18 322.783H8.81836C15.2354 346.597 24.9924 369.037 37.5734 389.566V389.566Z" fill="#D80027"/>
8
+ <path d="M118.584 39.978H141.913L120.213 55.743L128.502 81.252L106.803 65.487L85.104 81.252L92.264 59.215C73.158 75.13 56.412 93.776 42.612 114.552H50.087L36.274 124.587C34.122 128.177 32.058 131.824 30.08 135.525L36.676 155.826L24.37 146.885C21.311 153.366 18.513 159.993 15.998 166.758L23.265 189.126H50.087L28.387 204.891L36.676 230.4L14.977 214.635L1.979 224.079C0.678 234.537 0 245.189 0 256H256C256 114.616 256 97.948 256 0C205.428 0 158.285 14.67 118.584 39.978V39.978ZM128.502 230.4L106.803 214.635L85.104 230.4L93.393 204.891L71.693 189.126H98.515L106.803 163.617L115.091 189.126H141.913L120.213 204.891L128.502 230.4ZM120.213 130.317L128.502 155.826L106.803 140.061L85.104 155.826L93.393 130.317L71.693 114.552H98.515L106.803 89.043L115.091 114.552H141.913L120.213 130.317ZM220.328 230.4L198.629 214.635L176.93 230.4L185.219 204.891L163.519 189.126H190.341L198.629 163.617L206.917 189.126H233.739L212.039 204.891L220.328 230.4ZM212.039 130.317L220.328 155.826L198.629 140.061L176.93 155.826L185.219 130.317L163.519 114.552H190.341L198.629 89.043L206.917 114.552H233.739L212.039 130.317ZM212.039 55.743L220.328 81.252L198.629 65.487L176.93 81.252L185.219 55.743L163.519 39.978H190.341L198.629 14.469L206.917 39.978H233.739L212.039 55.743Z" fill="#0052B4"/>
9
+ </g>
10
+ <defs>
11
+ <clipPath id="clip0_3719_7206">
12
+ <rect width="512" height="512" fill="white"/>
13
+ </clipPath>
14
+ </defs>
15
+ </svg>
@@ -0,0 +1,9 @@
1
+ ---
2
+ const { class: className, ...rest } = Astro.props;
3
+ ---
4
+
5
+ <svg class={className} viewBox="0 0 28 28" fill="currentColor" {...rest}>
6
+ <path
7
+ d="M7.20236 5.41348C9.95373 7.46657 12.9137 11.6288 14 13.8625V19.762C14 19.6365 13.9512 19.7784 13.8463 20.0841C13.2792 21.7393 11.0644 28.1994 6.00012 23.035C3.33351 20.3159 4.56801 17.5967 9.42206 16.7758C6.64512 17.2453 3.52325 16.4694 2.66677 13.427C2.42 12.5519 2 7.16122 2 6.43315C2 2.78612 5.21712 3.93245 7.20236 5.41348ZM20.7976 5.41348C18.0463 7.46657 15.0863 11.6288 14 13.8625V19.762C14 19.6365 14.0487 19.7784 14.1537 20.0841C14.7206 21.7393 16.9354 28.1994 21.9997 23.035C24.6664 20.3159 23.432 17.5967 18.5779 16.7758C21.3547 17.2453 24.4767 16.4694 25.3331 13.427C25.58 12.5519 26 7.16122 26 6.43315C26 2.78612 22.7832 3.93245 20.7976 5.41348Z"
8
+ ></path>
9
+ </svg>
@@ -0,0 +1,8 @@
1
+ ---
2
+ const { class: className, ...rest } = Astro.props;
3
+ ---
4
+
5
+ <svg class={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" {...rest}>
6
+ <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
7
+ <polyline points="22,6 12,13 2,6"></polyline>
8
+ </svg>
@@ -0,0 +1,9 @@
1
+ ---
2
+ const { class: className, ...rest } = Astro.props;
3
+ ---
4
+
5
+ <svg class={className} viewBox="0 0 24 24" fill="currentColor" {...rest}>
6
+ <path
7
+ d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"
8
+ ></path>
9
+ </svg>
@@ -0,0 +1,9 @@
1
+ ---
2
+ const { class: className, ...rest } = Astro.props;
3
+ ---
4
+
5
+ <svg class={className} viewBox="0 0 28 28" fill="currentColor" {...rest}>
6
+ <path
7
+ d="M3.00384 9.88076C3.00384 16.0778 2.65684 23.7748 8.56284 25.3668C10.6948 25.9408 12.5268 26.0628 14.0008 25.9768C16.6748 25.8268 18.1758 25.0078 18.1758 25.0078L18.0858 23.0308C18.0858 23.0308 16.1748 23.6408 14.0268 23.5708C11.8998 23.4958 9.65684 23.3358 9.30984 20.6788C9.27784 20.4438 9.26284 20.1968 9.26284 19.9338C13.7698 21.0538 17.6118 20.4218 18.6708 20.2928C21.6248 19.9338 24.1988 18.0808 24.5258 16.3878C25.0408 13.7208 24.9998 9.87976 24.9998 9.87976C24.9998 4.67376 21.6468 3.14676 21.6468 3.14676C18.3558 1.60976 9.61784 1.62576 6.35884 3.14676C6.35684 3.14876 3.00384 4.67476 3.00384 9.88076ZM7.92684 7.21376C9.28984 5.66576 12.1278 5.56376 13.3918 7.54076L14.0028 8.58476L14.6138 7.54076C15.8828 5.55376 18.7258 5.67676 20.0788 7.21376C21.3268 8.67676 21.0478 10.0518 21.0478 16.5868H21.0458H18.5928V10.4708C18.5928 7.80876 15.2238 7.70676 15.2238 10.8398V14.1878H12.7868V10.8398C12.7868 7.70676 9.41784 7.80876 9.41784 10.4708V16.5878H6.95884C6.95884 10.0478 6.68484 8.66576 7.92684 7.21376Z"
8
+ ></path>
9
+ </svg>
@@ -0,0 +1,9 @@
1
+ ---
2
+ const { class: className, ...rest } = Astro.props;
3
+ ---
4
+
5
+ <svg class={className} viewBox="0 0 24 24" fill="currentColor" {...rest}>
6
+ <path
7
+ d="M13.54 12a6.8 6.8 0 01-6.77 6.82A6.8 6.8 0 010 12a6.8 6.8 0 016.77-6.82A6.8 6.8 0 0113.54 12zM20.96 12c0 3.54-1.51 6.42-3.38 6.42-1.87 0-3.39-2.88-3.39-6.42s1.52-6.42 3.39-6.42 3.38 2.88 3.38 6.42M24 12c0 3.17-.53 5.75-1.19 5.75-.66 0-1.19-2.58-1.19-5.75s.53-5.75 1.19-5.75C23.47 6.25 24 8.83 24 12z"
8
+ ></path>
9
+ </svg>
@@ -0,0 +1,11 @@
1
+ ---
2
+ const { class: className, ...rest } = Astro.props;
3
+ ---
4
+
5
+ <svg class={className} viewBox="0 0 28 28" fill="currentColor" {...rest}>
6
+ <path
7
+ fillRule="evenodd"
8
+ clipRule="evenodd"
9
+ d="M17.57 17.7956C16.673 17.7956 15.919 16.9304 15.919 15.8636C15.919 14.7974 16.673 13.9034 17.57 13.9034C18.467 13.9034 19.1945 14.7974 19.1945 15.8636C19.1945 16.9304 18.467 17.7956 17.57 17.7956ZM17.951 21.5444C17.1155 22.5374 15.827 23.0198 14.012 23.0198L13.9995 23.0186L13.9865 23.0198C12.1715 23.0198 10.884 22.5374 10.049 21.5444C9.9881 21.4724 9.93972 21.3866 9.90671 21.292C9.87369 21.1974 9.85669 21.0959 9.85669 20.9933C9.85669 20.8907 9.87369 20.7892 9.90671 20.6946C9.93972 20.5999 9.9881 20.5142 10.049 20.4422C10.1725 20.296 10.339 20.214 10.5125 20.214C10.686 20.214 10.8525 20.296 10.976 20.4422C11.552 21.1274 12.5365 21.461 13.9865 21.461L13.9995 21.4616L14.012 21.461C15.462 21.461 16.447 21.1274 17.024 20.4422C17.1474 20.2959 17.314 20.2138 17.4875 20.2138C17.661 20.2138 17.8275 20.2959 17.951 20.4422C18.2065 20.7476 18.2065 21.2402 17.951 21.5444ZM8.80551 15.8636C8.80551 14.798 9.55801 13.9034 10.454 13.9034C11.351 13.9034 12.0785 14.798 12.0785 15.8636C12.0785 16.9304 11.351 17.7956 10.454 17.7956C9.55751 17.7956 8.80551 16.9304 8.80551 15.8636ZM21.998 3.55881C22.6055 3.55881 23.0995 4.14682 23.0995 4.86802C23.0978 5.21684 22.9809 5.55061 22.7744 5.79614C22.5679 6.04168 22.2887 6.17893 21.998 6.17781C21.7073 6.17893 21.4281 6.04168 21.2216 5.79614C21.0151 5.55061 20.8982 5.21684 20.8965 4.86802C20.8965 4.14682 21.3905 3.55881 21.998 3.55881ZM26 13.742C26 11.8508 24.7065 10.3124 23.1155 10.3124C22.454 10.3114 21.8124 10.5832 21.298 11.0822C19.5395 9.76762 17.3075 8.97559 14.915 8.80819L16.163 4.11619L19.593 5.07621C19.683 6.56181 20.7265 7.73721 21.998 7.73721C23.328 7.73721 24.41 6.45022 24.41 4.86802C24.41 3.28702 23.3275 2 21.998 2C21.5573 1.99952 21.1248 2.1436 20.7474 2.41664C20.37 2.68968 20.0619 3.08128 19.8565 3.54918L15.8715 2.43319C15.7111 2.38757 15.5422 2.41613 15.3986 2.51321C15.255 2.61028 15.1471 2.76873 15.0965 2.95698L13.547 8.78062C10.969 8.85502 8.54599 9.65 6.65799 11.0408C6.15081 10.568 5.52672 10.3117 4.88449 10.3124C3.29349 10.3124 2 11.8508 2 13.742C2 14.9114 2.495 15.9452 3.2485 16.5638C3.217 16.8362 3.20099 17.1098 3.20099 17.387C3.20099 19.751 4.35649 21.9506 6.45499 23.579C8.46649 25.1402 11.129 26 13.9515 26C16.7735 26 19.436 25.1402 21.4475 23.579C23.546 21.9506 24.702 19.751 24.702 17.387C24.702 17.135 24.6875 16.8842 24.661 16.6352C25.4655 16.0262 26 14.957 26 13.742Z"
10
+ ></path>
11
+ </svg>
@@ -0,0 +1,11 @@
1
+ ---
2
+ const { class: className, ...rest } = Astro.props;
3
+ ---
4
+
5
+ <svg class={className} viewBox="0 0 28 28" fill="currentColor" {...rest}>
6
+ <path
7
+ fillRule="evenodd"
8
+ clipRule="evenodd"
9
+ d="M1.78736 13.4709C8.76667 10.3661 13.4206 8.31934 15.7493 7.33042C22.398 4.50685 23.7795 4.01637 24.68 4.00017C24.878 3.99661 25.3208 4.04673 25.6077 4.28437C25.8499 4.48504 25.9165 4.75611 25.9484 4.94636C25.9803 5.13661 26.02 5.57001 25.9884 5.90866C25.6281 9.7739 24.0692 19.1538 23.276 23.483C22.9404 25.3148 22.2796 25.929 21.6399 25.9891C20.2496 26.1197 19.1938 25.051 17.8473 24.1497C15.7401 22.7394 14.5498 21.8615 12.5044 20.4853C10.1407 18.8949 11.673 18.0208 13.0201 16.5922C13.3726 16.2184 19.4983 10.5294 19.6169 10.0134C19.6317 9.94881 19.6455 9.70822 19.5055 9.58118C19.3655 9.45414 19.1589 9.49758 19.0098 9.53213C18.7985 9.5811 15.4323 11.8528 8.91132 16.3472C7.95585 17.0171 7.09041 17.3435 6.31501 17.3264C5.46019 17.3075 3.81586 16.8329 2.59347 16.4272C1.09416 15.9296 -0.0974649 15.6665 0.00629874 14.8214C0.0603452 14.3812 0.654034 13.931 1.78736 13.4709Z"
10
+ ></path>
11
+ </svg>
@@ -0,0 +1,9 @@
1
+ ---
2
+ const { class: className, ...rest } = Astro.props;
3
+ ---
4
+
5
+ <svg class={className} viewBox="0 0 24 24" fill="currentColor" {...rest}>
6
+ <path
7
+ d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"
8
+ ></path>
9
+ </svg>
@@ -0,0 +1,59 @@
1
+ ---
2
+ import { ClientRouter } from 'astro:transitions';
3
+ import BaseHead, { type Props as HeadProps } from '../components/BaseHead.astro';
4
+ import Footer from '../components/Footer.astro';
5
+ import MaugliFloatingLabel from '../components/MaugliFloatingLabel.astro';
6
+ import Header from '../components/Header.astro';
7
+ import Nav from '../components/Nav.astro';
8
+
9
+ export type Props = HeadProps & { showHeader?: boolean; fullWidth?: boolean };
10
+
11
+ const { showHeader = true, fullWidth = false, ...head } = Astro.props;
12
+ ---
13
+
14
+ <!doctype html>
15
+ <html lang="ru" class="antialiased break-words bg-main">
16
+ <head>
17
+ <BaseHead {...head} />
18
+ <script>
19
+ // Инициализация темы до загрузки DOM
20
+ function initTheme() {
21
+ const theme = localStorage.getItem('theme');
22
+ if (theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
23
+ document.documentElement.classList.add('dark');
24
+ localStorage.setItem('theme', 'dark');
25
+ } else {
26
+ document.documentElement.classList.remove('dark');
27
+ localStorage.setItem('theme', 'light');
28
+ }
29
+ }
30
+
31
+ initTheme();
32
+ </script>
33
+ <ClientRouter />
34
+ </head>
35
+ <body class="bg-main text-main">
36
+ <div class="flex flex-col min-h-screen px-4 md:px-8 w-full">
37
+ <Nav />
38
+ {showHeader && <Header />}
39
+ <main class:list={['grow w-full mx-auto', fullWidth ? 'max-w-none' : 'max-w-3xl', 'pt-24']}>
40
+ <slot />
41
+ </main>
42
+ <Footer />
43
+ <MaugliFloatingLabel />
44
+ </div>
45
+
46
+ <script>
47
+ // Сохранение темы при переходах между страницами
48
+ document.addEventListener('astro:page-load', () => {
49
+ // Переинициализируем тему на каждой новой странице
50
+ const theme = localStorage.getItem('theme');
51
+ if (theme === 'dark') {
52
+ document.documentElement.classList.add('dark');
53
+ } else {
54
+ document.documentElement.classList.remove('dark');
55
+ }
56
+ });
57
+ </script>
58
+ </body>
59
+ </html>
@@ -0,0 +1,24 @@
1
+ ---
2
+ import Button from '../components/Button.astro';
3
+ import { maugliConfig } from '../config/maugli.config';
4
+ import { LANGUAGES } from '../i18n/languages';
5
+ import BaseLayout from '../layouts/BaseLayout.astro';
6
+
7
+ const lang = maugliConfig.defaultLang || 'en';
8
+ const languageObj = LANGUAGES.find((l) => l.code === lang) || LANGUAGES.find((l) => l.code === 'en');
9
+ const t = languageObj?.dict || {};
10
+ ---
11
+
12
+ <BaseLayout title={t.notFound.title} showHeader={false}>
13
+ <main class="flex flex-col items-center justify-center min-h-[60vh] w-full max-w-lg gap-8 p-8 rounded-xl card-shadow card-bg card-blur mx-auto">
14
+ <svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg" class="mb-4">
15
+ <circle cx="60" cy="60" r="56" fill="var(--brand-color, #ab3026)" fill-opacity="0.08" stroke="var(--brand-color, #ab3026)" stroke-width="8"
16
+ ></circle>
17
+ <circle cx="60" cy="60" r="32" fill="var(--brand-color, #ab3026)" fill-opacity="0.18"></circle>
18
+ <text x="60" y="75" text-anchor="middle" font-size="48" fill="var(--brand-color, #ab3026)">404</text>
19
+ </svg>
20
+ <h1 class="text-3xl font-bold mb-2 text-center">{t.notFound.title}</h1>
21
+ <p class="text-lg text-center mb-6">{t.notFound.description}</p>
22
+ <Button href="/" class="mt-2">{t.notFound.button}</Button>
23
+ </main>
24
+ </BaseLayout>
@@ -0,0 +1,50 @@
1
+ ---
2
+ import { getCollection, render, type CollectionEntry } from 'astro:content';
3
+ import HeroImage from '../components/HeroImage.astro';
4
+ import { maugliConfig } from '../config/maugli.config';
5
+ import { LANGUAGES } from '../i18n/languages';
6
+ import BaseLayout from '../layouts/BaseLayout.astro';
7
+
8
+ export async function getStaticPaths() {
9
+ const pages = await getCollection('pages');
10
+ return pages.map((page) => {
11
+ return {
12
+ params: { id: page.id },
13
+ props: { page }
14
+ };
15
+ });
16
+ }
17
+
18
+ type Props = { page: CollectionEntry<'pages'> };
19
+
20
+ const { page } = Astro.props;
21
+ const { title, seo } = page.data;
22
+ const { Content } = await render(page);
23
+
24
+ const dicts: Record<string, any> = Object.fromEntries(LANGUAGES.map((l) => [l.code, l.dict]));
25
+ const lang: string = typeof maugliConfig.defaultLang === 'string' && dicts[maugliConfig.defaultLang] ? maugliConfig.defaultLang : 'en';
26
+ const dict = dicts[lang] || dicts['en'];
27
+
28
+ const pageTitle = seo?.title ?? title ?? dict.pages?.defaultTitle ?? dicts['en'].pages?.defaultTitle ?? 'Page';
29
+ const pageDesc = seo?.description ?? dict.pages?.defaultDescription ?? dicts['en'].pages?.defaultDescription ?? '';
30
+ ---
31
+
32
+ <BaseLayout title={pageTitle} description={pageDesc} image={seo?.image} showHeader={false} fullWidth={true}>
33
+ <article class="mb-16 sm:mb-24">
34
+ <HeroImage
35
+ src={page.data.seo?.image?.src || '/default.webp'}
36
+ alt={page.data.seo?.image?.alt || page.data.title || 'Без названия'}
37
+ caption={page.data.seo?.image?.caption}
38
+ width={page.data.seo?.image?.width || 1200}
39
+ height={page.data.seo?.image?.height || 630}
40
+ srcset={page.data.seo?.image?.srcset}
41
+ sizes="(max-width: 640px) 100vw, (max-width: 1200px) 80vw, 1200px"
42
+ />
43
+ <header class="mb-8">
44
+ <h1 class="text-3xl leading-tight font-serif font-[800] sm:text-5xl sm:leading-tight" style="color: var(--text-heading)">{title}</h1>
45
+ </header>
46
+ <div class="max-w-none prose sm:prose-lg">
47
+ <Content />
48
+ </div>
49
+ </article>
50
+ </BaseLayout>
File without changes
@@ -0,0 +1,105 @@
1
+ ---
2
+ import type { GetStaticPathsOptions, Page } from 'astro';
3
+ import { getEntry, type CollectionEntry } from 'astro:content';
4
+ import { getFilteredCollection } from '../../utils/content-loader';
5
+ import AuthorCard from '../../components/AuthorCard.astro';
6
+ import Breadcrumbs from '../../components/Breadcrumbs.astro';
7
+ import InfoCard from '../../components/InfoCard.astro';
8
+ import Pagination from '../../components/Pagination.astro';
9
+ import Subscribe from '../../components/Subscribe.astro';
10
+ import { maugliConfig } from '../../config/maugli.config';
11
+ import { LANGUAGES } from '../../i18n/languages';
12
+ import BaseLayout from '../../layouts/BaseLayout.astro';
13
+
14
+ const dicts: Record<string, any> = Object.fromEntries(LANGUAGES.map((l) => [l.code, l.dict]));
15
+ const lang: string = typeof maugliConfig.defaultLang === 'string' && dicts[maugliConfig.defaultLang] ? maugliConfig.defaultLang : 'en';
16
+ const dict = dicts[lang] || dicts['en'];
17
+
18
+ export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
19
+ const authorsCollection = await getFilteredCollection('authors');
20
+ // Сортируем авторов по имени в алфавитном порядке
21
+ const authors = authorsCollection.sort((a, b) => a.data.name.localeCompare(b.data.name, 'ru'));
22
+ return paginate(authors, { pageSize: 4 }); // 4 автора на страницу для красивой сетки 2x2
23
+ }
24
+
25
+ type Props = { page: Page<CollectionEntry<'authors'>> };
26
+
27
+ const { page } = Astro.props;
28
+ let authors = page.data;
29
+
30
+ // Получаем все статьи
31
+ let allPosts = await getFilteredCollection('blog');
32
+
33
+ // Фильтрация авторов по наличию статей, если showAuthorsWithoutArticles === false
34
+ if (maugliConfig.showAuthorsWithoutArticles === false) {
35
+ authors = authors.filter((author) => {
36
+ const authorId = author.id;
37
+ // Always show default author
38
+ if (authorId === maugliConfig.defaultAuthorId) return true;
39
+ return allPosts.some((post) => {
40
+ const postAuthor = post.data.author;
41
+ if (Array.isArray(postAuthor)) {
42
+ return postAuthor.includes(authorId);
43
+ }
44
+ return postAuthor === authorId;
45
+ });
46
+ });
47
+ }
48
+
49
+ // Prepare data for AuthorCard
50
+ const authorsForCards = authors.map((author) => ({
51
+ name: author.data.name,
52
+ position: author.data.position,
53
+ description: author.data.description,
54
+ avatar: author.data.avatar,
55
+ socials: author.data.socials,
56
+ slug: author.id
57
+ }));
58
+ const authorsSection = await getEntry('pages', 'authors');
59
+
60
+ // Функция для подстановки переменных в строку
61
+ function interpolate(str: string, vars: Record<string, string>) {
62
+ return str.replace(/\{(\w+)\}/g, (_: string, k: string) => vars[k] ?? '');
63
+ }
64
+
65
+ const authorsTitle = maugliConfig.pageTitles?.authors?.trim() || dict.pages?.authors?.title || dicts['en'].pages.authors.title;
66
+ const authorsDescription =
67
+ maugliConfig.authorsDescription?.trim() ||
68
+ interpolate(dict.pages?.authors?.description || dicts['en'].pages.authors.description, { brand: maugliConfig.brand.name });
69
+ ---
70
+
71
+ <BaseLayout
72
+ title={authorsTitle}
73
+ description={authorsDescription}
74
+ image={{ src: '/img/default/autor_default.webp', alt: 'Команда Maugli Content Farm' }}
75
+ showHeader={false}
76
+ fullWidth={true}
77
+ >
78
+ <div class="max-w-[1280px] mx-auto">
79
+ <Breadcrumbs />
80
+
81
+ <div class="mb-12 sm:mb-16">
82
+ <h1 class="mb-4 text-3xl leading-tight font-serif font-[800] sm:text-5xl" style="color: var(--text-heading)">{authorsTitle}</h1>
83
+ <p class="text-lg text-[var(--text-main)] opacity-80 max-w-2xl">{authorsDescription}</p>
84
+ </div>
85
+
86
+ <!-- Сетка карточек авторов (2 колонки для лучшего отображения) -->
87
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-12 mb-16 justify-center">
88
+ {
89
+ authorsForCards.map((author) => (
90
+ <div class="flex justify-center">
91
+ <AuthorCard author={author} class="max-w-[360px] min-w-[360px]" />
92
+ </div>
93
+ ))
94
+ }
95
+ </div>
96
+
97
+ <!-- Пагинация (показывается только если больше одной страницы) -->
98
+ {page.lastPage > 1 && <Pagination page={page} class="my-16 sm:my-24" />}
99
+
100
+ {authorsSection && <InfoCard title={authorsSection.data.title} description={authorsSection.body} jsonld={authorsSection.data.jsonld} class="mt-16" />}
101
+ </div>
102
+
103
+ <!-- Подписка -->
104
+ <Subscribe class="my-16 sm:my-24" />
105
+ </BaseLayout>
@@ -0,0 +1,175 @@
1
+ ---
2
+ import { type CollectionEntry, render } from 'astro:content';
3
+ import Breadcrumbs from '../../components/Breadcrumbs.astro';
4
+ import PostPreview from '../../components/PostPreview.astro';
5
+ import Subscribe from '../../components/Subscribe.astro';
6
+ import TagsSection from '../../components/TagsSection.astro';
7
+ import { maugliConfig } from '../../config/maugli.config';
8
+ import BlueskyIcon from '../../icons/socials/BlueskyIcon.astro';
9
+ import EmailIcon from '../../icons/socials/EmailIcon.astro';
10
+ import LinkedinIcon from '../../icons/socials/LinkedinIcon.astro';
11
+ import MastodonIcon from '../../icons/socials/MastodonIcon.astro';
12
+ import MediumIcon from '../../icons/socials/MediumIcon.astro';
13
+ import RedditIcon from '../../icons/socials/RedditIcon.astro';
14
+ import TelegramIcon from '../../icons/socials/TelegramIcon.astro';
15
+ import TwitterIcon from '../../icons/socials/TwitterIcon.astro';
16
+ import BaseLayout from '../../layouts/BaseLayout.astro';
17
+ import { getFilteredCollection } from '../../utils/content-loader';
18
+ import { getAllTags, getPostsByTag, sortItemsWithFeaturedFirst } from '../../utils/data-utils';
19
+
20
+ export async function getStaticPaths() {
21
+ const authors = await getFilteredCollection('authors');
22
+ return authors.map((author) => ({
23
+ params: { id: author.id },
24
+ props: { author }
25
+ }));
26
+ }
27
+
28
+ type Props = { author: CollectionEntry<'authors'> };
29
+
30
+ const { author } = Astro.props;
31
+ const { name, position, description, avatar: rawAvatar, socials, seo } = author.data;
32
+ // Приводим путь к аватару к public/img/authors/...
33
+ const avatar = rawAvatar ? rawAvatar.replace('/img/examples/authors/', '/img/authors/') : undefined;
34
+ const { Content } = await render(author);
35
+
36
+ // Получаем все статьи блога
37
+ let allPosts = await getFilteredCollection('blog');
38
+
39
+ // Фильтруем статьи этого автора (с учетом дефолтного автора)
40
+ const authorPosts = allPosts
41
+ .filter(post => (post.data.author || maugliConfig.defaultAuthorId) === author.id)
42
+ .sort(sortItemsWithFeaturedFirst);
43
+
44
+ // Получаем теги статей автора
45
+ const authorTags = getAllTags(authorPosts).map((tag) => ({
46
+ id: tag.id,
47
+ name: tag.name,
48
+ count: getPostsByTag(authorPosts, tag.id).length
49
+ }));
50
+
51
+ // Получаем доступные соцсети
52
+ const availableSocials = socials ? Object.entries(socials).filter(([_, url]) => url) : [];
53
+
54
+ // Функция для получения компонента иконки
55
+ const getIconComponent = (platform: string) => {
56
+ switch (platform) {
57
+ case 'email':
58
+ return EmailIcon;
59
+ case 'telegram':
60
+ return TelegramIcon;
61
+ case 'mastodon':
62
+ return MastodonIcon;
63
+ case 'medium':
64
+ return MediumIcon;
65
+ case 'bluesky':
66
+ return BlueskyIcon;
67
+ case 'reddit':
68
+ return RedditIcon;
69
+ case 'linkedin':
70
+ return LinkedinIcon;
71
+ case 'twitter':
72
+ return TwitterIcon;
73
+ default:
74
+ return EmailIcon; // fallback
75
+ }
76
+ };
77
+
78
+ // SEO данные
79
+ const pageTitle = seo?.title || `${name} - ${position} | Maugli Content Farm`;
80
+ const pageDescription = seo?.description || description;
81
+ ---
82
+
83
+ <BaseLayout title={pageTitle} description={pageDescription} image={{ src: avatar || '/img/default/autor_default.webp', alt: `Фото ${name}` }} showHeader={false} fullWidth={true}>
84
+ <div class="max-w-[1280px] mx-auto">
85
+ <!-- Хлебные крошки -->
86
+ <Breadcrumbs />
87
+
88
+ <!-- Информация об авторе на плашке -->
89
+ <div class="mb-16 sm:mb-24">
90
+ <div class="bg-[var(--bg-muted)] rounded-custom p-8 md:p-12">
91
+ <div class="flex flex-col sm:flex-row gap-6 sm:gap-8 items-start">
92
+ <!-- Аватар -->
93
+ <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={`Фото ${name}`} class="w-full h-full object-cover" />
95
+ </div>
96
+
97
+ <!-- Информация -->
98
+ <div class="flex-grow">
99
+ <h1 class="mb-2 text-[4rem] leading-tight font-serif font-[800] sm:text-4xl" style="color: var(--text-heading)">
100
+ {name}
101
+ </h1>
102
+ <p class="text-[1rem] text-[var(--text-muted)] font-medium mb-4">
103
+ {position}
104
+ </p>
105
+ <p class="text-lg text-[var(--text-main)] leading-[1.35] mb-6">
106
+ <span style="opacity:0.7;">
107
+ {description}
108
+ </span>
109
+ </p>
110
+ <!-- Основной текст автора (после фронтматтера) -->
111
+ {Content && (
112
+ <div class="prose prose-lg max-w-none mb-6 text-[var(--text-main)]">
113
+ <Content />
114
+ </div>
115
+ )}
116
+
117
+ <!-- Соцсети -->
118
+ {
119
+ availableSocials.length > 0 && (
120
+ <div class="flex flex-wrap gap-3">
121
+ {availableSocials.map(([platform, url]) => {
122
+ const IconComponent = getIconComponent(platform);
123
+ return (
124
+ <a
125
+ href={platform === 'email' ? `mailto:${url}` : url as string}
126
+ target={platform === 'email' ? '_self' : '_blank'}
127
+ rel={platform === 'email' ? undefined : 'noopener noreferrer'}
128
+ class="inline-flex opacity-60 hover:opacity-100 items-center gap-2 px-4 py-2 border border-[var(--border-main)] rounded-custom hover:bg-[var(--bg-main)] transition-colors duration-200"
129
+ title={platform === 'email' ? `Написать ${name}` : `${name} в ${platform}`}
130
+ >
131
+ <IconComponent class="w-4 h-4 text-[var(--text-heading)] " />
132
+ <span class="text-sm font-medium text-[var(--text-heading)] capitalize">{platform}</span>
133
+ </a>
134
+ );
135
+ })}
136
+ </div>
137
+ )
138
+ }
139
+ </div>
140
+ </div>
141
+ </div>
142
+ </div>
143
+
144
+ <!-- Статьи автора -->
145
+ {
146
+ authorPosts.length > 0 && (
147
+ <div class="mb-16 sm:mb-24">
148
+ <h2 class="mb-12 text-2xl leading-tight font-serif font-[800] sm:mb-16 sm:text-3xl" style="color: var(--text-heading)">
149
+ Статьи автора
150
+ </h2>
151
+
152
+ <!-- Теги -->
153
+ {authorTags.length > 0 && (
154
+ <TagsSection
155
+ tags={authorTags}
156
+ totalCount={authorPosts.length}
157
+ basePath={`/authors/${author.id}`}
158
+ class="mb-8"
159
+ />
160
+ )}
161
+
162
+ <!-- Сетка статей -->
163
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
164
+ {authorPosts.map((post) => (
165
+ <PostPreview post={post} headingLevel="h3" />
166
+ ))}
167
+ </div>
168
+ </div>
169
+ )
170
+ }
171
+
172
+ <!-- Подписка -->
173
+ <Subscribe class="my-16 sm:my-24" />
174
+ </div>
175
+ </BaseLayout>
@@ -0,0 +1,59 @@
1
+ ---
2
+ import type { GetStaticPathsOptions, Page } from 'astro';
3
+ import { type CollectionEntry } from 'astro:content';
4
+ import { getFilteredCollection } from '../../utils/content-loader';
5
+ import Pagination from '../../components/Pagination.astro';
6
+ import PostPreview from '../../components/PostPreview.astro';
7
+ import Subscribe from '../../components/Subscribe.astro';
8
+ import TagsSection from '../../components/TagsSection.astro';
9
+ import { maugliConfig } from '../../config/maugli.config';
10
+ import siteConfig from '../../data/site-config';
11
+ import BaseLayout from '../../layouts/BaseLayout.astro';
12
+ import { getAllTags, getPostsByTag, sortItemsWithFeaturedFirst } from '../../utils/data-utils';
13
+
14
+ export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
15
+ let posts = (await getFilteredCollection('blog')).sort(sortItemsWithFeaturedFirst);
16
+ return paginate(posts, { pageSize: siteConfig.postsPerPage || 4 });
17
+ }
18
+
19
+ type Props = { page: Page<CollectionEntry<'blog'>> };
20
+
21
+ const { page } = Astro.props;
22
+ const blog = page.data;
23
+
24
+ // Получаем все теги с количеством постов и выделяем рубрики
25
+ let allPosts = await getFilteredCollection('blog');
26
+ let allRubrics = await getFilteredCollection('tags');
27
+ const rubricSlugs = allRubrics.map((r) => r.data.slug);
28
+ let tagsWithCount = getAllTags(allPosts).map((tag) => ({
29
+ id: tag.id,
30
+ name: tag.name,
31
+ count: getPostsByTag(allPosts, tag.id).length,
32
+ isRubric: rubricSlugs.includes(tag.id)
33
+ }));
34
+ if (maugliConfig.showOnlyRubricsTags) {
35
+ tagsWithCount = tagsWithCount.filter((tag) => tag.isRubric);
36
+ } else {
37
+ tagsWithCount = [...tagsWithCount.filter((tag) => tag.isRubric), ...tagsWithCount.filter((tag) => !tag.isRubric)];
38
+ }
39
+ ---
40
+
41
+ <BaseLayout
42
+ title="<Блог>"
43
+ description="Блог об автоматизации глазами ИИ"
44
+ image={{ src: '/tr-prewiew.png', alt: 'The preview of the site' }}
45
+ showHeader={false}
46
+ fullWidth={true}
47
+ >
48
+ <div class="max-w-[1280px] mx-auto">
49
+ <h1 class="mb-12 text-3xl leading-tight font-serif font-[800] sm:mb-16 sm:text-5xl" style="color: var(--text-heading)">Статьи</h1>
50
+ <TagsSection tags={tagsWithCount} totalCount={allPosts.length} />
51
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8 mb-16">
52
+ {blog.map((post) => <PostPreview post={post} />)}
53
+ </div>
54
+
55
+ <!-- Пагинация (показывается только если больше одной страницы) -->
56
+ {page.lastPage > 1 && <Pagination page={page} class="my-16 sm:my-24" />}
57
+ </div>
58
+ <Subscribe class="my-16 sm:my-24" />
59
+ </BaseLayout>