add AuthorDispaly

This commit is contained in:
Profile Profile
2026-02-02 17:58:36 +03:00
parent 49a4638c75
commit 131deebffc
9 changed files with 600 additions and 137 deletions

View File

@@ -0,0 +1,29 @@
---
// src/components/DisplayAuthor.astro
const { post } = Astro.props;
// Ваша логика, адаптированная для компонента
let authorDisplay = '';
if (post?.coauthors && post.coauthors.length > 0) {
authorDisplay = post.coauthors.map(author => {
if (author.firstName && author.lastName) {
return `<a href="${author.url || '#'}">${author.firstName} ${author.lastName}</a>`;
}
return `<a href="${author.url || '#'}">${author.name}</a>`;
}).join(' ');
} else if (post?.author?.node) {
const author = post.author.node;
const name = author.firstName && author.lastName
? `${author.firstName} ${author.lastName}`
: author.name;
authorDisplay = `<a href="${author.url || '#'}">${name}</a>`;
}
---
{authorDisplay ? (
<span set:html={authorDisplay} />
) : (
<span>Автор не указан</span>
)}

View File

@@ -0,0 +1,185 @@
---
import { getLatestColonPost } from '@lib/api/colon-posts';
import Author from '@components/AuthorDisplay.astro';
const colonPost = await getLatestColonPost();
---
{colonPost && (
<div class="colon-post-card">
<div class="colon-post-content">
<div class="colon-post-left">
{colonPost.featuredImage?.node?.sourceUrl && (
<a href={colonPost.uri}>
<img
src={colonPost.featuredImage.node.sourceUrl}
alt={colonPost.featuredImage.node.altText || colonPost.secondaryTitle || colonPost.title}
loading="lazy"
class="colon-post-image"
/>
</a>
)}
<div class="colon-post-meta">
<span>{new Date(colonPost.date).toLocaleDateString('ru-RU')}</span>
<div><Author post={colonPost} separator=", " /></div>
</div>
</div>
<div class="colon-post-right">
<a href={colonPost.uri}>
<h3>{colonPost.secondaryTitle || colonPost.title}</h3>
</a>
</div>
</div>
</div>
)}
<style>
.colon-post-card {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.colon-post-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.colon-post-content {
display: flex;
gap: 20px;
padding: 20px;
align-items: flex-start;
}
.colon-post-left {
flex: 0 0 200px;
position: relative;
}
.colon-post-image-link {
display: block;
overflow: hidden;
border-radius: 6px;
margin-bottom: 10px;
}
.colon-post-image {
width: 100%;
height: 150px;
object-fit: cover;
transition: transform 0.3s ease;
}
.colon-post-image:hover {
transform: scale(1.05);
}
.colon-post-image-placeholder {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 150px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 6px;
margin-bottom: 10px;
text-decoration: none;
}
.placeholder-text {
color: #666;
font-size: 14px;
}
.colon-post-meta {
display: flex;
align-items: center;
font-size: 12px;
color: #666;
line-height: 1.4;
}
.colon-post-date {
white-space: nowrap;
}
.meta-separator {
margin: 0 5px;
color: #ccc;
}
.colon-post-author {
white-space: nowrap;
}
.author-link {
color: #2c3e50;
text-decoration: none;
font-weight: 500;
}
.author-link:hover {
color: #3498db;
text-decoration: underline;
}
.colon-post-right {
flex: 1;
}
.colon-post-title-link {
text-decoration: none;
color: inherit;
display: block;
}
.colon-post-title {
margin: 0 0 10px 0;
font-size: 20px;
line-height: 1.4;
color: #2c3e50;
transition: color 0.3s ease;
}
.colon-post-title:hover {
color: #3498db;
}
.colon-post-secondary-title {
font-size: 16px;
color: #666;
line-height: 1.5;
font-weight: 400;
}
/* Адаптивность */
@media (max-width: 768px) {
.colon-post-content {
flex-direction: column;
gap: 15px;
}
.colon-post-left {
flex: 0 0 auto;
width: 100%;
}
.colon-post-image,
.colon-post-image-placeholder {
height: 200px;
}
.colon-post-title {
font-size: 18px;
}
.colon-post-secondary-title {
font-size: 15px;
}
}
</style>

View File

@@ -1,4 +1,7 @@
---
export interface Props {
items: any[];
showCount?: boolean;

View File

@@ -1,95 +1,191 @@
---
// src/components/MainPostWidget.astro
import { getLatestMainPost } from '@lib/api/main-posts';
import Author from '@components/AuthorDisplay.astro';
const mainPost = await getLatestMainPost();
const postDate = mainPost ? new Date(mainPost.date) : null;
if (!mainPost) return null;
const postDate = new Date(mainPost.date);
const imageUrl = mainPost?.featuredImage?.node?.sourceUrl;
const imageAlt = mainPost?.featuredImage?.node?.altText || mainPost?.title || '';
const isoDate = postDate.toISOString();
const category = mainPost?.categories?.nodes?.[0];
const categoryName = category?.name || '';
const categoryColor = category?.color || '#2271b1'; // цвет по умолчанию
const categoryColor = category?.color || '#2271b1';
const formattedDate = postDate.toLocaleDateString('ru-RU', {
day: 'numeric',
month: 'short',
year: 'numeric'
}).replace(' г.', '');
let authorName = '';
if (mainPost?.coauthors && mainPost.coauthors.length > 0) {
authorName = mainPost.coauthors.map(author => {
if (author.firstName && author.lastName) {
return `${author.firstName} ${author.lastName}`;
}
return author.name;
}).join(', ');
} else if (mainPost?.author) {
const author = mainPost.author.node;
authorName = author.firstName && author.lastName
? `${author.firstName} ${author.lastName}`
: author.name;
}
---
{mainPost && (
<article class="main-post-card" itemscope itemtype="https://schema.org/Article">
<a href={mainPost.uri} class="post-card-link">
<div class="post-image-container">
{imageUrl ? (
<img
src={imageUrl}
alt={imageAlt}
width="400"
height="400"
loading="lazy"
class="post-image"
itemprop="image"
/>
) : (
<div class="post-image-placeholder"></div>
)}
{categoryName && (
<div
class="post-category-badge"
style={`background-color: ${categoryColor}; color: white;`}
>
{categoryName}
</div>
)}
<div class="post-content-overlay">
<div class="post-meta-overlay">
<time
datetime={mainPost.date}
class="post-date-overlay"
itemprop="datePublished"
>
{postDate && postDate.toLocaleDateString('ru-RU', {
day: 'numeric',
month: 'short',
year: 'numeric'
}).replace(' г.', '')}
</time>
</div>
<h3 class="post-title-overlay" itemprop="headline">
{mainPost.title}
</h3>
{authorName && (
<div class="author-name" itemprop="author">
{authorName}
</div>
)}
<article
class="main-post-widget"
itemscope
itemtype="https://schema.org/Article"
itemid={mainPost.uri}
>
<a
href={mainPost.uri}
class="post-card-link"
aria-label={`Читать статью: ${mainPost.title}`}
>
<div class="image-container">
{imageUrl ? (
<img
src={imageUrl}
alt={imageAlt}
width="800"
height="450"
loading="eager"
class="post-image"
itemprop="image"
/>
) : (
<div class="image-placeholder" aria-hidden="true"></div>
)}
{categoryName && (
<div
class="category-badge"
style={`background-color: ${categoryColor}`}
>
{categoryName}
</div>
)}
<div class="content-overlay">
<div class="meta-overlay">
<time datetime={isoDate} class="date-overlay">
{formattedDate}
</time>
</div>
<h2 class="title-overlay">
{mainPost.title}
</h2>
{authorName && (
<div class="author-overlay">
<Author post={mainPost} separator=" " />
</div>
)}
</div>
</a>
<div class="sr-only">
<h3 itemprop="headline">
<a href={mainPost.uri} itemprop="url">{mainPost.title}</a>
</h3>
<time datetime={mainPost.date} itemprop="datePublished">
{postDate && postDate.toLocaleDateString('ru-RU')}
</time>
</div>
</article>
)}
</a>
<meta itemprop="url" content={mainPost.uri} />
<meta itemprop="datePublished" content={isoDate} />
{authorName && <meta itemprop="author" content={authorName} />}
{categoryName && <meta itemprop="articleSection" content={categoryName} />}
</article>
<style>
/* ОСНОВНОЕ: ограничиваем ширину виджета */
.main-post-widget {
width: 100%;
max-width: 800px; /* Вот это ключевое правило! */
margin: 0 auto; /* Центрируем */
border-radius: 8px;
overflow: hidden;
background: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.post-card-link {
display: block;
text-decoration: none;
color: inherit;
}
.image-container {
position: relative;
width: 100%;
aspect-ratio: 16/9;
overflow: hidden;
}
.post-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.post-card-link:hover .post-image {
transform: scale(1.03);
}
.image-placeholder {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #f5f5f5, #e0e0e0);
}
.category-badge {
position: absolute;
top: 16px;
left: 16px;
padding: 6px 12px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: white;
z-index: 2;
line-height: 1;
}
.content-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 24px;
background: linear-gradient(transparent, rgba(0,0,0,0.7) 70%);
color: white;
z-index: 1;
}
.date-overlay {
font-size: 0.875rem;
opacity: 0.9;
display: block;
}
.title-overlay {
margin: 0 0 12px 0;
font-size: 1.5rem;
font-weight: 700;
line-height: 1.3;
color: white;
}
.author-overlay {
font-size: 0.875rem;
opacity: 0.9;
font-style: italic;
}
/* Адаптивность */
@media (max-width: 1023px) {
.main-post-widget {
border-radius: 0;
max-width: 100%; /* На мобильных занимает всю ширину */
}
.content-overlay {
padding: 16px;
}
.title-overlay {
font-size: 1.25rem;
}
}
</style>

View File

@@ -10,6 +10,7 @@ export interface ColonPost {
id: string;
databaseId: number;
title: string;
secondaryTitle: string;
uri: string;
date: string;
colonItem: boolean;
@@ -40,24 +41,7 @@ export interface ColonPost {
url: string;
description: string;
}>;
categories: {
nodes: Array<{
id: string;
name: string;
color?: string;
slug: string;
uri: string;
databaseId: number;
}>;
};
tags: {
nodes: Array<{
id: string;
name: string;
slug: string;
uri: string;
}>;
};
}
/**
@@ -77,6 +61,7 @@ const LATEST_COLON_POST_QUERY = `
id
databaseId
title
secondaryTitle
uri
date
colonItem
@@ -107,24 +92,6 @@ const LATEST_COLON_POST_QUERY = `
url
description
}
categories {
nodes {
id
name
color
slug
uri
databaseId
}
}
tags {
nodes {
id
name
slug
uri
}
}
}
}
}

View File

@@ -1,17 +1,20 @@
---
import { getSiteInfo } from "../lib/wp-api.js";
import { getLatestPosts } from '@api/posts.js';
import { getLatestColonPost } from '@lib/api/colon-posts';
import '../styles/home.css';
const site = await getSiteInfo();
const { posts, pageInfo } = await getLatestPosts(41); // Сразу деструктурируем
const colonPost = await getLatestColonPost(); //последний пост колонки
// визуальные компоненты
import MainLayout from '@layouts/MainLayout.astro';
import ContentGrid from '@components/ContentGrid.astro';
import EndnewsList from '@components/EndnewsList.astro';
import MainPostWidget from '@/components/MainPostWidget.astro';
import MainLine from '@components/MainLine.astro';
//ISR
@@ -22,27 +25,20 @@ export const prerender = false;
title={site.title}
description="Информационное агентство Деловой журнал Профиль"
>
<h1>{site.title}</h1>
<h1>{site.title}</h1>mainline_1
{site.description && <p>{site.description}</p>}
<!-- index.astro -->
<MainPostWidget />
<MainLine />
{colonPost && (
<div>
<h3>{colonPost.title}</h3>
<a href={colonPost.uri}>Читать колонку</a>
</div>
)}
<div class="maimnewsline">
<EndnewsList />
</div>
<ContentGrid
<ContentGrid
items={posts}
pageInfo={pageInfo}
showCount={true}
loadMoreConfig={{ type: 'latest', first: 11 }}
/>
/>
</MainLayout>

View File

@@ -48,11 +48,6 @@ a {
}
.maimnewsline{
display: flex;
}
@media (max-width: 767px) {

56
src/styles/home.css Normal file
View File

@@ -0,0 +1,56 @@
/* Убедитесь, что эти стили применяются! */
.mainline_1 {
display: flex;
gap: 20px;
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
}
/* Левая колонка */
.endnews-container {
flex: 0 0 264px;
width: 264px;
}
/* Центральная колонка */
.colons {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center; /* Центрируем содержимое */
}
/* Ограничиваем ширину MainPostWidget внутри .colons */
.colons > * {
max-width: 800px;
width: 100%;
}
/* Правая колонка */
.main-partners {
flex: 0 0 300px;
width: 300px;
}
/* Адаптивность */
@media (max-width: 1023px) {
.mainline_1 {
flex-direction: column;
padding: 0 15px;
}
.endnews-container,
.colons,
.main-partners {
flex: none;
width: 100%;
max-width: 100%;
}
.colons > * {
max-width: 100%;
}
}

136
src/utils/author.ts Normal file
View File

@@ -0,0 +1,136 @@
// src/utils/author.ts
export interface Author {
id?: string;
slug?: string;
firstName?: string;
lastName?: string;
name?: string;
// Добавляем URL или slug для страницы автора
uri?: string;
url?: string;
}
export interface Coauthor extends Author {
// дополнительные поля, если есть
}
export interface PostData {
author?: {
node: Author;
};
coauthors?: Coauthor[];
// другие поля поста
}
// Интерфейс для результата
export interface AuthorInfo {
name: string;
url: string;
id?: string;
slug?: string;
}
export interface MultipleAuthorsResult {
type: 'single' | 'multiple';
authors: AuthorInfo[];
}
// Функция для получения информации об авторе(ах)
export function getAuthorInfo(post?: PostData | null): MultipleAuthorsResult {
if (!post) {
return {
type: 'single',
authors: [{ name: '', url: '' }]
};
}
// Обработка соавторов
if (post.coauthors && post.coauthors.length > 0) {
const authors = post.coauthors.map(author => ({
name: author.firstName && author.lastName
? `${author.firstName} ${author.lastName}`
: author.name || '',
url: getAuthorUrl(author),
id: author.id,
slug: author.slug
})).filter(author => author.name); // Убираем пустые имена
return {
type: authors.length > 1 ? 'multiple' : 'single',
authors
};
}
// Обработка основного автора
if (post.author?.node) {
const author = post.author.node;
const authorInfo = {
name: author.firstName && author.lastName
? `${author.firstName} ${author.lastName}`
: author.name || '',
url: getAuthorUrl(author),
id: author.id,
slug: author.slug
};
return {
type: 'single',
authors: [authorInfo]
};
}
return {
type: 'single',
authors: [{ name: '', url: '' }]
};
}
// Вспомогательная функция для формирования URL
function getAuthorUrl(author: Author): string {
// Приоритеты формирования URL:
// 1. Готовый URL
// 2. URI (часто из WordPress)
// 3. Slug
// 4. ID
// 5. Имя в формате slug
if (author.url) return author.url;
if (author.uri) return author.uri;
if (author.slug) return `/author/${author.slug}/`;
if (author.id) return `/author/${author.id}/`;
// Формируем slug из имени
const name = author.firstName && author.lastName
? `${author.firstName}-${author.lastName}`
: author.name || 'author';
return `/author/${slugify(name)}/`;
}
// Функция для slugify (можно вынести в отдельную утилиту)
function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^\w\s-]/g, '') // Удаляем спецсимволы
.replace(/\s+/g, '-') // Заменяем пробелы на дефисы
.replace(/--+/g, '-') // Убираем двойные дефисы
.trim();
}
// Сохраняем старую функцию для обратной совместимости
export function getAuthorName(post?: PostData | null): string {
const info = getAuthorInfo(post);
if (info.authors.length === 0) return '';
if (info.authors.length === 1) return info.authors[0].name;
// Для нескольких авторов
return info.authors.map(a => a.name).join(', ');
}
// Новая функция для получения URL первого автора
export function getAuthorUrlFromPost(post?: PostData | null): string {
const info = getAuthorInfo(post);
return info.authors[0]?.url || '';
}