From 1b5530392ff7e22cfeb66e72441f6d7a8c052ebd Mon Sep 17 00:00:00 2001 From: Profile Profile Date: Wed, 28 Jan 2026 12:02:55 +0300 Subject: [PATCH] add real authors --- src/components/Coauthors.astro | 200 +++++++++++++++ src/components/ContentGrid.astro | 32 +-- src/lib/api/posts.ts | 121 ++++----- src/pages/index.astro | 2 +- src/styles/components/ContentGrid.css | 347 ++++++++++++-------------- 5 files changed, 449 insertions(+), 253 deletions(-) create mode 100644 src/components/Coauthors.astro diff --git a/src/components/Coauthors.astro b/src/components/Coauthors.astro new file mode 100644 index 0000000..629463f --- /dev/null +++ b/src/components/Coauthors.astro @@ -0,0 +1,200 @@ +--- +export interface Props { + coauthors: any[]; + prefix?: string; + className?: string; +} + +const { + coauthors = [], + prefix = '', + className = '', +} = Astro.props; + +function getAuthorUrl(coauthor: any): string { + if (coauthor?.url) return coauthor.url; + if (coauthor?.uri) return coauthor.uri; + if (coauthor?.databaseId) return `/author/${coauthor.databaseId}`; + return '#'; +} +--- + +{coauthors.length > 0 && ( +
+ {prefix && {prefix}} + + {coauthors.map((coauthor, index) => { + const authorUrl = getAuthorUrl(coauthor); + const isLast = index === coauthors.length - 1; + + return ( + + + {coauthor.name} + + {!isLast && , } + + ); + })} +
+)} + + \ No newline at end of file diff --git a/src/components/ContentGrid.astro b/src/components/ContentGrid.astro index 850e06a..f9d50f0 100644 --- a/src/components/ContentGrid.astro +++ b/src/components/ContentGrid.astro @@ -6,19 +6,17 @@ export interface Props { const { items = [], - showCount = false, + showCount = false, } = Astro.props; // Функция для извлечения класса цвета из строки function extractColorClass(colorString: string): string { if (!colorString) return 'bg-blue'; - // Если строка содержит "фон меню:" - извлекаем часть после двоеточия if (colorString.includes('фон меню:')) { const parts = colorString.split(':'); const color = parts[1]?.trim(); - // Проверяем существование CSS класса const validColors = [ 'black', 'yellow', 'blue', 'green', 'red', 'orange', 'gray', 'indigo', 'purple', 'pink', 'teal', 'cyan', 'white', @@ -30,12 +28,10 @@ function extractColorClass(colorString: string): string { } } - // Если строка уже содержит "bg-" if (colorString.startsWith('bg-')) { return colorString; } - // Если это просто название цвета без префикса const simpleColor = colorString.toLowerCase(); switch(simpleColor) { case 'black': case 'yellow': case 'blue': case 'green': @@ -47,8 +43,17 @@ function extractColorClass(colorString: string): string { default: return 'bg-blue'; } } ---- +// Функция для получения списка имен соавторов +function getCoauthorsNames(coauthors: any[]): string { + if (!coauthors || coauthors.length === 0) return ''; + + return coauthors + .map((coauthor: any) => coauthor?.node?.name || coauthor?.name) + .filter(Boolean) + .join(' '); +} +---

@@ -61,12 +66,12 @@ function extractColorClass(colorString: string): string { {items.map((item, index) => { const postUrl = item.uri || `/blog/${item.databaseId}`; const postDate = new Date(item.date); + const coauthors = item.coauthors || []; + const coauthorsNames = getCoauthorsNames(coauthors); - // Получаем цвет категории и преобразуем в CSS класс const rawColor = item.categories?.nodes?.[0]?.color || ''; const categoryBgClass = extractColorClass(rawColor); - // Логика для больших плиток на десктопе let isLarge = false; let largePosition = ''; @@ -93,7 +98,7 @@ function extractColorClass(colorString: string): string {
{item.featuredImage?.node?.sourceUrl ? ( - {item.featuredImage.node.altText
)} - {/* Рубрика в верхнем правом углу с цветом */} {item.categories?.nodes?.[0]?.name && (
{item.categories.nodes[0].name}
)} - {/* Оверлей с контентом */}
- {/* Скринридеру и SEO */}

@@ -158,4 +160,4 @@ function extractColorClass(colorString: string): string { ); })}

-

\ No newline at end of file + diff --git a/src/lib/api/posts.ts b/src/lib/api/posts.ts index 62ba8df..b6f82b2 100644 --- a/src/lib/api/posts.ts +++ b/src/lib/api/posts.ts @@ -11,7 +11,7 @@ export interface AnewsPost { date: string; } -export async function getLatestPosts(first = 12, after = null) { +export async function getLatestPosts(first = 14, after = null) { // Создаем уникальный ключ для кэша const cacheKey = `latest-posts:${first}:${after || 'first-page'}`; @@ -20,63 +20,72 @@ export async function getLatestPosts(first = 12, after = null) { async () => { const query = ` query GetLatestProfileArticles($first: Int!, $after: String) { - profileArticles( - first: $first, - after: $after, - where: {orderby: { field: DATE, order: DESC }} - ) { - pageInfo { - hasNextPage - endCursor - } - edges { - cursor - node { - id - databaseId - title - uri - date - featuredImage { - node { - sourceUrl(size: LARGE) - altText - } - } - author { - node { - id - name - firstName - lastName - avatar { - url - } - uri - } - } - categories { - nodes { - id - name - color - slug - uri - databaseId - } - } - tags { - nodes { - id - name - slug - uri - } - } - } - } + profileArticles( + first: $first + after: $after + where: { orderby: { field: DATE, order: DESC } } + ) { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + id + databaseId + title + uri + date + featuredImage { + node { + sourceUrl(size: LARGE) + altText } } + author { + node { + id + name + firstName + lastName + avatar { + url + } + uri + } + } + # Соавторы как массив + coauthors { + id + name + firstName + lastName + url + description + } + categories { + nodes { + id + name + color + slug + uri + databaseId + } + } + tags { + nodes { + id + name + slug + uri + } + } + } + } + } +} `; const data = await fetchGraphQL(query, { first, after }); diff --git a/src/pages/index.astro b/src/pages/index.astro index 753f4a1..6e31374 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -3,7 +3,7 @@ import { getSiteInfo } from "../lib/wp-api.js"; import { getLatestPosts } from '@api/posts.js'; const site = await getSiteInfo(); -const initialPosts = await getLatestPosts(36); // Начальная загрузка 12 постов +const initialPosts = await getLatestPosts(37); // Начальная загрузка 12 постов // визуальные компоненты import MainLayout from '@layouts/MainLayout.astro'; diff --git a/src/styles/components/ContentGrid.css b/src/styles/components/ContentGrid.css index 44a4246..a5cee93 100644 --- a/src/styles/components/ContentGrid.css +++ b/src/styles/components/ContentGrid.css @@ -1,6 +1,16 @@ /* styles/components/ContentGrid.css */ -/* Глобальные стили (только для этого компонента) */ +/* CSS переменные */ +:root { + --grid-gap: 20px; + --grid-gap-tablet: 15px; + --border-radius: 10px; + --transition-speed: 0.3s; + --overlay-padding: 20px 15px 15px; + --overlay-padding-large: 25px 20px 20px; +} + +/* Секция постов */ .posts-section { max-width: 1400px; margin: 0 auto; @@ -14,69 +24,71 @@ padding-top: 20px; } +/* Сетка постов */ .posts-grid { - display: flex; - flex-wrap: wrap; - gap: 20px; + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--grid-gap); width: 100%; - margin: 0; - padding: 0; } -/* ОСНОВНОЙ СТИЛЬ: ВСЕ ПЛИТКИ КВАДРАТНЫЕ */ +/* Карточка поста - базовые стили */ .post-card { - flex: 1 0 calc(25% - 15px); /* 4 колонки по умолчанию */ background: white; - border-radius: 10px; + border-radius: var(--border-radius); overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.08); - transition: transform 0.3s ease, box-shadow 0.3s ease; - min-width: 0; + transition: transform var(--transition-speed) ease, box-shadow var(--transition-speed) ease; aspect-ratio: 1 / 1; position: relative; + display: flex; + flex-direction: column; + height: 0; + padding-bottom: 100%; /* Aspect ratio hack для надежности */ } -/* Большие плитки на десктопе - ТОЖЕ КВАДРАТНЫЕ */ +/* Большие карточки (десктоп) */ @media (min-width: 1200px) { .post-card-large { - flex: 0 0 calc(50% - 10px) !important; /* Занимает 2 колонки */ - aspect-ratio: 2 / 1 !important; /* Ширина в 2 раза больше высоты */ + grid-column: span 2; + aspect-ratio: 2 / 1; + padding-bottom: 50%; } } +/* Hover эффекты */ .post-card:hover { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0,0,0,0.15); } +.post-card:hover .post-image { + transform: scale(1.05); +} + +/* Ссылка карточки */ .post-card-link { + position: absolute; + inset: 0; display: block; text-decoration: none; color: inherit; - width: 100%; - height: 100%; - position: absolute; - top: 0; - left: 0; } +/* Контейнер изображения */ .post-image-container { - position: relative; - width: 100%; - height: 100%; + position: absolute; + inset: 0; overflow: hidden; } +/* Изображение */ .post-image { width: 100%; height: 100%; object-fit: cover; display: block; - transition: transform 0.3s ease; -} - -.post-card:hover .post-image { - transform: scale(1.05); + transition: transform var(--transition-speed) ease; } .post-image-placeholder { @@ -85,7 +97,7 @@ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } -/* Бейдж рубрики */ +/* Бейдж категории */ .post-category-badge { position: absolute; top: 15px; @@ -104,23 +116,15 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - text-align: right; } -@media (min-width: 1200px) { - .post-card-large .post-category-badge { - font-size: 12px; - padding: 6px 12px; - } -} - -/* Оверлей с текстом */ +/* Оверлей с контентом */ .post-content-overlay { position: absolute; bottom: 0; left: 0; right: 0; - padding: 20px 15px 15px; + padding: var(--overlay-padding); background: linear-gradient( to top, rgba(0, 0, 0, 0.9) 0%, @@ -133,13 +137,6 @@ gap: 8px; } -@media (min-width: 1200px) { - .post-card-large .post-content-overlay { - padding: 25px 20px 20px; - gap: 10px; - } -} - .post-meta-overlay { margin-bottom: 3px; } @@ -147,17 +144,9 @@ .post-date-overlay { font-size: 12px; color: rgba(255, 255, 255, 0.9); - opacity: 0.9; - display: block; font-weight: 400; } -@media (min-width: 1200px) { - .post-card-large .post-date-overlay { - font-size: 13px; - } -} - .post-title-overlay { font-size: 16px; line-height: 1.3; @@ -171,14 +160,6 @@ text-overflow: ellipsis; } -@media (min-width: 1200px) { - .post-card-large .post-title-overlay { - font-size: 18px; - line-height: 1.4; - -webkit-line-clamp: 3; - } -} - .author-name { font-size: 12px; color: rgba(255, 255, 255, 0.85); @@ -186,14 +167,131 @@ font-weight: 400; } +/* Улучшения для больших карточек */ @media (min-width: 1200px) { + .post-card-large .post-category-badge { + font-size: 12px; + padding: 6px 12px; + } + + .post-card-large .post-content-overlay { + padding: var(--overlay-padding-large); + gap: 10px; + } + + .post-card-large .post-date-overlay { + font-size: 13px; + } + + .post-card-large .post-title-overlay { + font-size: 18px; + line-height: 1.4; + } + .post-card-large .author-name { font-size: 13px; margin-top: 5px; } } -/* Для скринридеров */ +/* Ноутбуки: 3 колонки */ +@media (min-width: 992px) and (max-width: 1199px) { + .posts-grid { + grid-template-columns: repeat(3, 1fr); + } + + .post-card-large { + grid-column: span 1; + aspect-ratio: 1 / 1; + padding-bottom: 100%; + } +} + +/* Планшеты: 2 колонки */ +@media (min-width: 768px) and (max-width: 991px) { + .posts-grid { + grid-template-columns: repeat(2, 1fr); + gap: var(--grid-gap-tablet); + } + + .post-card-large { + grid-column: span 1; + aspect-ratio: 1 / 1; + padding-bottom: 100%; + } + + .post-title-overlay { + font-size: 15px; + } +} + +/* Мобильные: 1 колонка */ +@media (max-width: 767px) { + .posts-grid { + grid-template-columns: 1fr; + gap: var(--grid-gap-tablet); + } + + .post-card { + aspect-ratio: 2 / 1; + padding-bottom: 50%; + } + + .post-card-large { + aspect-ratio: 2 / 1; + padding-bottom: 50%; + } + + .posts-section { + padding: 0 15px 15px; + } + + .posts-section h2 { + padding-top: 15px; + } + + .post-content-overlay { + padding: 15px 12px 12px; + } + + .post-title-overlay { + font-size: 15px; + } + + .post-category-badge { + top: 12px; + right: 12px; + font-size: 10px; + padding: 4px 8px; + } +} + +/* Очень маленькие экраны */ +@media (max-width: 480px) { + .post-card, + .post-card-large { + aspect-ratio: 3 / 2; + padding-bottom: 66.67%; + } + + .post-content-overlay { + padding: 12px 10px 10px; + gap: 5px; + } + + .post-title-overlay { + font-size: 14px; + } + + .post-category-badge { + top: 10px; + right: 10px; + padding: 3px 6px; + font-size: 9px; + } +} + +/* Вспомогательные классы */ .sr-only { position: absolute; width: 1px; @@ -206,7 +304,7 @@ border: 0; } -/* Стили для индикаторов загрузки */ +/* Состояния загрузки */ .loading-indicator { text-align: center; padding: 40px 0; @@ -238,91 +336,14 @@ margin-top: 20px; } -/* Адаптивность */ - -/* Для ноутбуков: 3 в ряд (без больших плиток) */ -@media (min-width: 992px) and (max-width: 1199px) { - .posts-grid { - align-items: stretch; - } - - .post-card { - flex: 1 0 calc(33.333% - 14px); - aspect-ratio: 1 / 1; - } - - .post-card-large { - flex: 1 0 calc(33.333% - 14px) !important; - aspect-ratio: 1 / 1 !important; - margin-left: 0 !important; - order: initial !important; - } +.no-posts { + text-align: center; + padding: 40px; + color: #666; + font-size: 18px; } -/* Для планшетов: 2 в ряд */ -@media (min-width: 768px) and (max-width: 991px) { - .posts-grid { - gap: 15px; - } - - .post-card { - flex: 1 0 calc(50% - 10px); - aspect-ratio: 1 / 1; - } - - .post-card-large { - flex: 1 0 calc(50% - 10px) !important; - aspect-ratio: 1 / 1 !important; - margin-left: 0 !important; - order: initial !important; - } - - .post-title-overlay { - font-size: 15px; - } -} - -/* Для мобильных: 1 в ряд */ @media (max-width: 767px) { - .posts-grid { - gap: 15px; - } - - .post-card { - flex: 1 0 100%; - aspect-ratio: 2 / 1; /* На мобильных горизонтальные плитки */ - } - - .post-card-large { - flex: 1 0 100% !important; - aspect-ratio: 2 / 1 !important; - margin-left: 0 !important; - order: initial !important; - } - - .posts-section { - padding: 0 15px 15px; - } - - .posts-section h2 { - padding-top: 15px; - } - - .post-content-overlay { - padding: 15px 12px 12px; - } - - .post-title-overlay { - font-size: 15px; - } - - .post-category-badge { - top: 12px; - right: 12px; - font-size: 10px; - padding: 4px 8px; - } - .loading-spinner { width: 30px; height: 30px; @@ -332,39 +353,3 @@ padding: 20px 0; } } - -/* Для очень маленьких экранов */ -@media (max-width: 480px) { - .post-card { - aspect-ratio: 3 / 2; /* Немного более вертикальные на маленьких экранах */ - } - - .post-card-large { - aspect-ratio: 3 / 2 !important; - } - - .post-content-overlay { - padding: 12px 10px 10px; - gap: 5px; - } - - .post-title-overlay { - font-size: 14px; - -webkit-line-clamp: 3; - } - - .post-category-badge { - top: 10px; - right: 10px; - padding: 3px 6px; - font-size: 9px; - } -} - -/* Стиль для пустого состояния */ -.no-posts { - text-align: center; - padding: 40px; - color: #666; - font-size: 18px; -} \ No newline at end of file