add real authors

This commit is contained in:
Profile Profile
2026-01-28 12:02:55 +03:00
parent c01845bb39
commit 1b5530392f
5 changed files with 449 additions and 253 deletions

View File

@@ -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 && (
<div
class={`coauthors-inline ${className}`}
itemprop="contributor"
itemtype="https://schema.org/Person"
>
{prefix && <span class="coauthors-prefix">{prefix}</span>}
{coauthors.map((coauthor, index) => {
const authorUrl = getAuthorUrl(coauthor);
const isLast = index === coauthors.length - 1;
return (
<span class="coauthor-wrapper" key={coauthor.id || index}>
<a
href={authorUrl}
class="coauthor-name"
itemprop="name"
title={coauthor.name}
>
{coauthor.name}
</a>
{!isLast && <span class="comma">, </span>}
</span>
);
})}
</div>
)}
<style>
/* Основные стили компонента */
.coauthors-inline {
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
display: flex;
flex-wrap: wrap;
align-items: baseline;
margin-top: 3px;
line-height: 1.3;
font-weight: 400;
}
/* Префикс (если используется) */
.coauthors-prefix {
font-weight: 400;
color: rgba(255, 255, 255, 0.85);
margin-right: 4px;
white-space: nowrap;
}
/* Обертка для каждого автора */
.coauthor-wrapper {
display: inline-flex;
align-items: center;
}
/* Ссылка на автора */
.coauthor-name {
color: rgba(255, 255, 255, 0.85);
text-decoration: none;
transition: all 0.2s ease;
font-weight: 400;
}
/* Ховер эффект для ссылки */
.coauthor-name:hover {
color: rgba(255, 255, 255, 1);
text-decoration: underline;
}
/* Фокус состояние для доступности */
.coauthor-name:focus {
outline: 2px solid rgba(255, 255, 255, 0.7);
outline-offset: 2px;
border-radius: 2px;
}
/* Запятая между авторами */
.comma {
color: rgba(255, 255, 255, 0.7);
margin: 0 2px;
}
/* Стили для больших карточек (post-card-large) */
.post-card-large .coauthors-inline {
font-size: 13px;
margin-top: 5px;
}
/* Адаптивные стили */
/* Планшеты */
@media (max-width: 991px) {
.coauthors-inline {
font-size: 11px;
}
}
/* Мобильные устройства */
@media (max-width: 767px) {
.coauthors-inline {
font-size: 11px;
}
}
/* Очень маленькие экраны */
@media (max-width: 480px) {
.coauthors-inline {
font-size: 10px;
}
}
/* Варианты модификаторов класса */
/* Темная тема для светлого фона */
.coauthors-inline.dark-theme {
color: rgba(0, 0, 0, 0.85);
}
.coauthors-inline.dark-theme .coauthors-prefix {
color: rgba(0, 0, 0, 0.85);
}
.coauthors-inline.dark-theme .coauthor-name {
color: rgba(0, 0, 0, 0.85);
}
.coauthors-inline.dark-theme .coauthor-name:hover {
color: rgba(0, 0, 0, 1);
}
.coauthors-inline.dark-theme .comma {
color: rgba(0, 0, 0, 0.7);
}
/* Без префикса */
.coauthors-inline.no-prefix {
gap: 0;
}
.coauthors-inline.no-prefix .coauthors-prefix {
display: none;
}
/* Компактный вариант */
.coauthors-inline.compact {
font-size: 11px;
margin-top: 2px;
line-height: 1.2;
}
/* Расширенный вариант */
.coauthors-inline.expanded {
font-size: 14px;
margin-top: 6px;
line-height: 1.4;
}
/* Вариант с жирным текстом */
.coauthors-inline.bold .coauthor-name {
font-weight: 500;
}
/* Вариант с курсивом */
.coauthors-inline.italic .coauthor-name {
font-style: italic;
}
/* Вариант с подчеркиванием при наведении */
.coauthors-inline.underline-on-hover .coauthor-name:hover {
text-decoration: underline;
}
/* Вариант без подчеркивания */
.coauthors-inline.no-underline .coauthor-name:hover {
text-decoration: none;
opacity: 0.9;
}
</style>

View File

@@ -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(' ');
}
---
<section class="posts-section" id="posts-section">
<h2>
@@ -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 {
<a href={postUrl} class="post-card-link">
<div class="post-image-container">
{item.featuredImage?.node?.sourceUrl ? (
<img loading="lazy"
<img
src={item.featuredImage.node.sourceUrl}
alt={item.featuredImage.node.altText || item.title}
width="400"
@@ -106,14 +111,12 @@ function extractColorClass(colorString: string): string {
<div class="post-image-placeholder"></div>
)}
{/* Рубрика в верхнем правом углу с цветом */}
{item.categories?.nodes?.[0]?.name && (
<div class={`post-category-badge ${categoryBgClass}`}>
{item.categories.nodes[0].name}
</div>
)}
{/* Оверлей с контентом */}
<div class="post-content-overlay">
<div class="post-meta-overlay">
<time
@@ -133,16 +136,15 @@ function extractColorClass(colorString: string): string {
{item.title}
</h3>
{item.author?.node?.name && (
{coauthorsNames && (
<div class="author-name" itemprop="author">
{item.author.node.name}
{coauthorsNames}
</div>
)}
</div>
</div>
</a>
{/* Скринридеру и SEO */}
<div class="sr-only">
<h3 itemprop="headline">
<a href={postUrl} itemprop="url">{item.title}</a>

View File

@@ -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 });

View File

@@ -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';

View File

@@ -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;
}