add new logic contentgrid
This commit is contained in:
@@ -11,7 +11,8 @@ export interface Props {
|
|||||||
hasNextPage: boolean;
|
hasNextPage: boolean;
|
||||||
endCursor: string | null;
|
endCursor: string | null;
|
||||||
};
|
};
|
||||||
perLoad?: number;
|
perLoad?: number; // Больше не используется, но оставляем для обратной совместимости
|
||||||
|
gridColumns?: 3 | 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -20,13 +21,15 @@ const {
|
|||||||
type = 'latest',
|
type = 'latest',
|
||||||
slug = '',
|
slug = '',
|
||||||
pageInfo = { hasNextPage: false, endCursor: null },
|
pageInfo = { hasNextPage: false, endCursor: null },
|
||||||
perLoad = 11
|
perLoad = 11, // Игнорируется
|
||||||
|
gridColumns = 4
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
|
// Конфиг без perLoad, так как будем вычислять на клиенте
|
||||||
const loadMoreConfig = {
|
const loadMoreConfig = {
|
||||||
type,
|
type,
|
||||||
slug,
|
slug,
|
||||||
perLoad
|
gridColumns
|
||||||
};
|
};
|
||||||
|
|
||||||
function getCoauthorsNames(coauthors: any[]): string {
|
function getCoauthorsNames(coauthors: any[]): string {
|
||||||
@@ -35,17 +38,22 @@ function getCoauthorsNames(coauthors: any[]): string {
|
|||||||
return coauthors
|
return coauthors
|
||||||
.map((coauthor: any) => {
|
.map((coauthor: any) => {
|
||||||
const name = coauthor?.node?.name || coauthor?.name;
|
const name = coauthor?.node?.name || coauthor?.name;
|
||||||
const nickname = coauthor?.node?.nickname || coauthor?.nickname;
|
return name;
|
||||||
|
|
||||||
return name; // Возвращаем только имя, ссылки будут в шаблоне
|
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldBeLarge(index: number): boolean {
|
function shouldBeLarge(index: number, columns: number): boolean {
|
||||||
if (index < 8) return false;
|
if (columns === 4) {
|
||||||
return (index - 8) % 11 === 0;
|
// Паттерн для 4 колонок: большие на позициях 8, 19, 30, 41...
|
||||||
|
if (index < 8) return false;
|
||||||
|
return (index - 8) % 11 === 0;
|
||||||
|
} else {
|
||||||
|
// Паттерн для 3 колонок: большие на позициях 6, 14, 22, 30...
|
||||||
|
if (index < 6) return false;
|
||||||
|
return (index - 6) % 8 === 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -56,16 +64,21 @@ function shouldBeLarge(index: number): boolean {
|
|||||||
)}
|
)}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div id="posts-grid" class="posts-grid">
|
<div
|
||||||
|
id="posts-grid"
|
||||||
|
class="posts-grid"
|
||||||
|
data-grid-columns={gridColumns}
|
||||||
|
class:list={[`posts-grid-${gridColumns}`]}
|
||||||
|
>
|
||||||
{items.map((item, index) => {
|
{items.map((item, index) => {
|
||||||
const postUrl = item.uri || `/blog/${item.databaseId}`;
|
const postUrl = item.uri || `/blog/${item.databaseId}`;
|
||||||
const postDate = new Date(item.date);
|
const postDate = new Date(item.date);
|
||||||
const isLarge = shouldBeLarge(index);
|
const isLarge = shouldBeLarge(index, gridColumns);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
class={`post-card ${isLarge ? 'post-card-large' : ''}`}
|
class={`post-card ${isLarge ? 'post-card-large' : ''}`}
|
||||||
data-large-position={isLarge ? 'first' : ''}
|
data-large={isLarge}
|
||||||
data-index={index}
|
data-index={index}
|
||||||
itemscope
|
itemscope
|
||||||
itemtype="https://schema.org/BlogPosting"
|
itemtype="https://schema.org/BlogPosting"
|
||||||
@@ -85,15 +98,14 @@ function shouldBeLarge(index: number): boolean {
|
|||||||
<div class="post-image-placeholder"></div>
|
<div class="post-image-placeholder"></div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{item.categories?.nodes?.[0] && (
|
||||||
{item.categories?.nodes?.[0] && (
|
<CategoryBadge
|
||||||
<CategoryBadge
|
name={item.categories.nodes[0].name}
|
||||||
name={item.categories.nodes[0].name}
|
color={item.categories.nodes[0].color}
|
||||||
color={item.categories.nodes[0].color}
|
href={`/${item.categories.nodes[0].slug}`}
|
||||||
href={`/${item.categories.nodes[0].slug}`}
|
isNews={item.__typename === "ANew"}
|
||||||
isNews={item.__typename === "ANew"}
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
<div class="post-content-overlay">
|
<div class="post-content-overlay">
|
||||||
<div class="post-meta-overlay">
|
<div class="post-meta-overlay">
|
||||||
@@ -121,7 +133,6 @@ function shouldBeLarge(index: number): boolean {
|
|||||||
{item.coauthors.map((coauthor: any, i: number) => {
|
{item.coauthors.map((coauthor: any, i: number) => {
|
||||||
const name = coauthor?.node?.name || coauthor?.name;
|
const name = coauthor?.node?.name || coauthor?.name;
|
||||||
const nickname = coauthor?.node?.nickname || coauthor?.nickname;
|
const nickname = coauthor?.node?.nickname || coauthor?.nickname;
|
||||||
console.log(nickname);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span class="author-name" key={nickname || name}>
|
<span class="author-name" key={nickname || name}>
|
||||||
@@ -161,40 +172,27 @@ function shouldBeLarge(index: number): boolean {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Индикатор загрузки -->
|
|
||||||
<div id="loading-indicator" class="loading-indicator" style="display: none;">
|
<div id="loading-indicator" class="loading-indicator" style="display: none;">
|
||||||
<div class="loading-spinner"></div>
|
<div class="loading-spinner"></div>
|
||||||
<p>Загрузка...</p>
|
<p>Загрузка...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Сообщение об окончании -->
|
|
||||||
<div id="no-more-posts" class="no-more-posts" style="display: none;">
|
<div id="no-more-posts" class="no-more-posts" style="display: none;">
|
||||||
Все статьи загружены
|
Все статьи загружены
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sentinel для Intersection Observer -->
|
|
||||||
{pageInfo.hasNextPage && (
|
{pageInfo.hasNextPage && (
|
||||||
<div
|
<div
|
||||||
id="infinity-scroll-sentinel"
|
id="infinity-scroll-sentinel"
|
||||||
data-end-cursor={pageInfo.endCursor}
|
data-end-cursor={pageInfo.endCursor}
|
||||||
data-load-config={JSON.stringify(loadMoreConfig)}
|
data-load-config={JSON.stringify(loadMoreConfig)}
|
||||||
data-current-index={items.length}
|
data-current-index={items.length}
|
||||||
|
data-grid-columns={gridColumns}
|
||||||
></div>
|
></div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
interface PageInfo {
|
|
||||||
hasNextPage: boolean;
|
|
||||||
endCursor: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoadMoreConfig {
|
|
||||||
type: 'latest' | 'category' | 'author' | 'tag';
|
|
||||||
slug?: string;
|
|
||||||
perLoad: number; // Только perLoad, никакого first
|
|
||||||
}
|
|
||||||
|
|
||||||
class InfinityScroll {
|
class InfinityScroll {
|
||||||
private grid: HTMLElement | null;
|
private grid: HTMLElement | null;
|
||||||
private sentinel: HTMLElement | null;
|
private sentinel: HTMLElement | null;
|
||||||
@@ -206,7 +204,18 @@ function shouldBeLarge(index: number): boolean {
|
|||||||
private hasMore = true;
|
private hasMore = true;
|
||||||
private endCursor: string | null = null;
|
private endCursor: string | null = null;
|
||||||
private currentIndex: number;
|
private currentIndex: number;
|
||||||
private loadMoreConfig: LoadMoreConfig;
|
private gridColumns: 3 | 4 = 4;
|
||||||
|
|
||||||
|
// Константы для разных сеток
|
||||||
|
private readonly CYCLE_LENGTH = {
|
||||||
|
3: 14, // Полный цикл для 3 колонок: 6 обычных + 1 большая + 6 обычных + 1 большая
|
||||||
|
4: 19 // Полный цикл для 4 колонок: 8 обычных + 1 большая + 9 обычных + 1 большая
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly OPTIMAL_LOADS = {
|
||||||
|
3: [9, 12, 15], // 3, 4, 5 полных рядов
|
||||||
|
4: [12, 16, 20] // 3, 4, 5 полных рядов
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.grid = document.getElementById('posts-grid');
|
this.grid = document.getElementById('posts-grid');
|
||||||
@@ -215,32 +224,11 @@ function shouldBeLarge(index: number): boolean {
|
|||||||
this.noMorePosts = document.getElementById('no-more-posts');
|
this.noMorePosts = document.getElementById('no-more-posts');
|
||||||
this.postsCount = document.getElementById('posts-count');
|
this.postsCount = document.getElementById('posts-count');
|
||||||
|
|
||||||
// Дефолтный конфиг только с perLoad
|
if (!this.sentinel) return;
|
||||||
const defaultConfig: LoadMoreConfig = {
|
|
||||||
type: 'latest',
|
|
||||||
perLoad: 11
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.sentinel) {
|
this.endCursor = this.sentinel.dataset.endCursor || null;
|
||||||
this.endCursor = this.sentinel.dataset.endCursor || null;
|
this.currentIndex = parseInt(this.sentinel.dataset.currentIndex || '0');
|
||||||
this.currentIndex = parseInt(this.sentinel.dataset.currentIndex || '0');
|
this.gridColumns = parseInt(this.sentinel.dataset.gridColumns || '4') as 3 | 4;
|
||||||
|
|
||||||
try {
|
|
||||||
// Парсим конфиг из data-атрибута
|
|
||||||
const parsedConfig = JSON.parse(this.sentinel.dataset.loadConfig || '{}');
|
|
||||||
this.loadMoreConfig = {
|
|
||||||
...defaultConfig,
|
|
||||||
...parsedConfig,
|
|
||||||
// Убеждаемся, что perLoad определен
|
|
||||||
perLoad: parsedConfig.perLoad || defaultConfig.perLoad
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
this.loadMoreConfig = defaultConfig;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.loadMoreConfig = defaultConfig;
|
|
||||||
this.currentIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
@@ -265,6 +253,29 @@ function shouldBeLarge(index: number): boolean {
|
|||||||
this.observer.observe(this.sentinel);
|
this.observer.observe(this.sentinel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Главная функция: определяет оптимальное количество постов для загрузки
|
||||||
|
* Гарантирует, что после загрузки не будет "дырок" в сетке
|
||||||
|
*/
|
||||||
|
private getOptimalLoadCount(): number {
|
||||||
|
const columns = this.gridColumns;
|
||||||
|
const cycleLength = this.CYCLE_LENGTH[columns];
|
||||||
|
const position = this.currentIndex % cycleLength;
|
||||||
|
const options = this.OPTIMAL_LOADS[columns];
|
||||||
|
|
||||||
|
// Выбираем оптимальное число в зависимости от позиции в цикле
|
||||||
|
if (position < columns) {
|
||||||
|
// Мы в начале цикла - можно загрузить 3 полных ряда
|
||||||
|
return options[0];
|
||||||
|
} else if (position < columns * 2) {
|
||||||
|
// Мы в середине цикла - лучше загрузить 4 полных ряда
|
||||||
|
return options[1];
|
||||||
|
} else {
|
||||||
|
// Мы ближе к концу цикла - загружаем 5 полных рядов
|
||||||
|
return options[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async loadMorePosts() {
|
private async loadMorePosts() {
|
||||||
if (this.isLoading || !this.hasMore) return;
|
if (this.isLoading || !this.hasMore) return;
|
||||||
|
|
||||||
@@ -272,64 +283,61 @@ function shouldBeLarge(index: number): boolean {
|
|||||||
this.showLoading();
|
this.showLoading();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Отправляем только perLoad (никакого first)
|
const loadCount = this.getOptimalLoadCount();
|
||||||
|
|
||||||
const response = await fetch('/load-more-posts', {
|
const response = await fetch('/load-more-posts', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
perLoad: this.loadMoreConfig.perLoad,
|
perLoad: loadCount, // Используем оптимальное число
|
||||||
after: this.endCursor,
|
after: this.endCursor,
|
||||||
type: this.loadMoreConfig.type,
|
type: this.loadMoreConfig?.type || 'latest',
|
||||||
slug: this.loadMoreConfig.slug,
|
slug: this.loadMoreConfig?.slug || '',
|
||||||
startIndex: this.currentIndex
|
startIndex: this.currentIndex,
|
||||||
|
gridColumns: this.gridColumns
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Ошибка загрузки постов: ${response.status}`);
|
throw new Error(`Ошибка загрузки: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const html = await response.text();
|
const html = await response.text();
|
||||||
|
|
||||||
const temp = document.createElement('div');
|
const temp = document.createElement('div');
|
||||||
temp.innerHTML = html;
|
temp.innerHTML = html;
|
||||||
|
|
||||||
const newSentinel = temp.querySelector('#infinity-scroll-sentinel');
|
const newSentinel = temp.querySelector('#infinity-scroll-sentinel');
|
||||||
let newEndCursor = null;
|
const hasNextPage = !!newSentinel;
|
||||||
let hasNextPage = false;
|
const newEndCursor = newSentinel?.dataset.endCursor || null;
|
||||||
|
|
||||||
if (newSentinel) {
|
|
||||||
newEndCursor = newSentinel.dataset.endCursor || null;
|
|
||||||
hasNextPage = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const articles = temp.querySelectorAll('article');
|
const articles = temp.querySelectorAll('article');
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
|
|
||||||
articles.forEach(article => {
|
if (articles.length > 0) {
|
||||||
fragment.appendChild(article.cloneNode(true));
|
const fragment = document.createDocumentFragment();
|
||||||
});
|
articles.forEach(article => fragment.appendChild(article.cloneNode(true)));
|
||||||
|
this.grid?.appendChild(fragment);
|
||||||
|
|
||||||
this.grid?.appendChild(fragment);
|
this.currentIndex += articles.length;
|
||||||
|
this.endCursor = newEndCursor;
|
||||||
|
this.hasMore = hasNextPage;
|
||||||
|
|
||||||
// Используем perLoad для увеличения индекса
|
if (this.postsCount) {
|
||||||
this.currentIndex += this.loadMoreConfig.perLoad;
|
this.postsCount.textContent = ` (${this.currentIndex})`;
|
||||||
this.endCursor = newEndCursor;
|
|
||||||
this.hasMore = hasNextPage;
|
|
||||||
|
|
||||||
if (this.postsCount) {
|
|
||||||
this.postsCount.textContent = ` (${this.currentIndex})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.sentinel) {
|
|
||||||
this.sentinel.dataset.currentIndex = String(this.currentIndex);
|
|
||||||
this.sentinel.dataset.endCursor = newEndCursor || '';
|
|
||||||
|
|
||||||
if (!hasNextPage) {
|
|
||||||
this.showNoMorePosts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.sentinel) {
|
||||||
|
this.sentinel.dataset.currentIndex = String(this.currentIndex);
|
||||||
|
this.sentinel.dataset.endCursor = newEndCursor || '';
|
||||||
|
|
||||||
|
if (!hasNextPage) {
|
||||||
|
this.showNoMorePosts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.hasMore = false;
|
||||||
|
this.showNoMorePosts();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки:', error);
|
console.error('Ошибка загрузки:', error);
|
||||||
@@ -379,19 +387,16 @@ function shouldBeLarge(index: number): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let infinityScroll: InfinityScroll | null = null;
|
// Инициализация
|
||||||
|
if (document.getElementById('infinity-scroll-sentinel')) {
|
||||||
if ('requestIdleCallback' in window) {
|
if ('requestIdleCallback' in window) {
|
||||||
requestIdleCallback(() => {
|
requestIdleCallback(() => {
|
||||||
infinityScroll = new InfinityScroll();
|
new InfinityScroll();
|
||||||
}, { timeout: 2000 });
|
}, { timeout: 2000 });
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
infinityScroll = new InfinityScroll();
|
new InfinityScroll();
|
||||||
}, 200);
|
}, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('astro:before-swap', () => {
|
|
||||||
infinityScroll?.destroy();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
11
src/components/Header/HeaderLine.astro
Normal file
11
src/components/Header/HeaderLine.astro
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
import CurrentDate from '@components/Header/CurrentDate.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="header-info">
|
||||||
|
<div class="header__widgets">
|
||||||
|
<CurrentDate />
|
||||||
|
<div class="header__currency">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -90,7 +90,7 @@ const { post, pageInfo } = Astro.props;
|
|||||||
<style>
|
<style>
|
||||||
.article-wrapper {
|
.article-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 75%;
|
max-width: 90%;
|
||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
101
src/layouts/ContentLayout.astro
Normal file
101
src/layouts/ContentLayout.astro
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
const { title, description, category, contentType } = Astro.props;
|
||||||
|
|
||||||
|
import Header from '../components/Header/Header.astro';
|
||||||
|
import HeaderLine from '../components/Header/HeaderLine.astro';
|
||||||
|
|
||||||
|
import Footer from '../components/Footer.astro';
|
||||||
|
|
||||||
|
import '../styles/global.css';
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>{`${title}`} - Деловой журнал Профиль</title>
|
||||||
|
<meta name="description" content={description}>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<HeaderLine />
|
||||||
|
<div class="container">
|
||||||
|
<Header category={category}
|
||||||
|
contentType={contentType}/>
|
||||||
|
<main class="content-main">
|
||||||
|
<div class="content">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<aside class="sidebar">
|
||||||
|
<div class="banner"></div>
|
||||||
|
</aside>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Footer
|
||||||
|
publicationName="Профиль"
|
||||||
|
organization="Учредитель: ИДР. Все права защищены."
|
||||||
|
menuItems={[
|
||||||
|
{ text: "О нас", url: "/about" },
|
||||||
|
{ text: "Контакты", url: "/contacts" },
|
||||||
|
{ text: "Политика конфиденциальности", url: "/privacy" },
|
||||||
|
{ text: "Архив", url: "/archive" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.content-main{
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 2; /* Занимает 2 части */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
/*flex: 1; Занимает 1 часть */
|
||||||
|
min-width: 250px; /* Минимальная ширина для сайдбара */
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
min-width: 260px;
|
||||||
|
max-width: 260px;
|
||||||
|
min-height: 200px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
|
||||||
|
.content-main{
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1; /* Контент на всю ширину */
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
const { title, description, category, contentType } = Astro.props;
|
const { title, description, category, contentType } = Astro.props;
|
||||||
|
|
||||||
import Header from '../components/Header/Header.astro';
|
import Header from '../components/Header/Header.astro';
|
||||||
import Header_lite from '../components/Header/Header_lite.astro';
|
import HeaderLine from '../components/Header/HeaderLine.astro';
|
||||||
import CurrentDate from '../components/Header/CurrentDate.astro';
|
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from '../components/Footer.astro';
|
||||||
|
|
||||||
import '../styles/global.css';
|
import '../styles/global.css';
|
||||||
@@ -20,13 +20,7 @@ import '../styles/global.css';
|
|||||||
<meta name="description" content={description}>
|
<meta name="description" content={description}>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="header-info">
|
<HeaderLine />
|
||||||
<div class="header__widgets">
|
|
||||||
<CurrentDate />
|
|
||||||
<div class="header__currency">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<Header category={category}
|
<Header category={category}
|
||||||
contentType={contentType}/>
|
contentType={contentType}/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import MainLayout from '@layouts/MainLayout.astro';
|
import ContentLayout from '@layouts/ContentLayout.astro';
|
||||||
import NewsSingle from '@components/NewsSingle.astro';
|
import NewsSingle from '@components/NewsSingle.astro';
|
||||||
import ContentGrid from '@components/ContentGrid.astro';
|
import ContentGrid from '@components/ContentGrid.astro';
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ if (pageInfo.type === 'single') { //одиночная статья
|
|||||||
//);
|
//);
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainLayout
|
<ContentLayout
|
||||||
title={title}
|
title={title}
|
||||||
description="Информационное агентство Деловой журнал Профиль"
|
description="Информационное агентство Деловой журнал Профиль"
|
||||||
category={category}
|
category={category}
|
||||||
@@ -84,11 +84,12 @@ if (pageInfo.type === 'single') { //одиночная статья
|
|||||||
slug={pageInfo.categorySlug}
|
slug={pageInfo.categorySlug}
|
||||||
showCount={false}
|
showCount={false}
|
||||||
type='category'
|
type='category'
|
||||||
perLoad={11}
|
perLoad={12}
|
||||||
|
gridColumns={3}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</MainLayout>
|
</ContentLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
import MainLayout from '@layouts/MainLayout.astro';
|
import ContentLayout from '@layouts/ContentLayout.astro';
|
||||||
import ContentGrid from '@components/ContentGrid.astro';
|
import ContentGrid from '@components/ContentGrid.astro';
|
||||||
|
|
||||||
import { getLatestPosts } from '@api/posts.js';
|
import { getLatestPosts } from '@api/posts.js';
|
||||||
@@ -13,7 +13,7 @@ export const prerender = false;
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainLayout
|
<ContentLayout
|
||||||
title=Статьи
|
title=Статьи
|
||||||
description= Статьи
|
description= Статьи
|
||||||
>
|
>
|
||||||
@@ -24,9 +24,10 @@ export const prerender = false;
|
|||||||
items={posts}
|
items={posts}
|
||||||
pageInfo={pageInfo}
|
pageInfo={pageInfo}
|
||||||
type="latest"
|
type="latest"
|
||||||
|
gridColumns={3}
|
||||||
perLoad={11}
|
perLoad={11}
|
||||||
showCount={false}
|
showCount={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
</MainLayout>
|
</ContentLayout>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import MainLayout from '@layouts/MainLayout.astro';
|
import ContentLayout from '@layouts/ContentLayout.astro';
|
||||||
import NewsSingle from '@components/NewsSingle.astro';
|
import NewsSingle from '@components/NewsSingle.astro';
|
||||||
|
|
||||||
import { detectPageType } from '@lib/detect-page-type';
|
import { detectPageType } from '@lib/detect-page-type';
|
||||||
@@ -48,7 +48,7 @@ if (import.meta.env.DEV) {
|
|||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainLayout
|
<ContentLayout
|
||||||
description="Информационное агентство Деловой журнал Профиль"
|
description="Информационное агентство Деловой журнал Профиль"
|
||||||
category={category}
|
category={category}
|
||||||
contentType="news"
|
contentType="news"
|
||||||
@@ -66,4 +66,4 @@ if (import.meta.env.DEV) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</MainLayout>
|
</ContentLayout>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
import MainLayout from '@layouts/MainLayout.astro';
|
import ContentLayout from '@layouts/ContentLayout.astro';
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainLayout
|
<ContentLayout
|
||||||
title=Новости
|
title=Новости
|
||||||
description=Новости
|
description=Новости
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -24,15 +24,33 @@
|
|||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Сетка постов */
|
/* ===== СЕТКИ С РАЗНЫМ КОЛИЧЕСТВОМ КОЛОНОК ===== */
|
||||||
.posts-grid {
|
|
||||||
|
/* Сетка на 4 колонки (по умолчанию) */
|
||||||
|
.posts-grid-4 {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: var(--grid-gap);
|
gap: var(--grid-gap);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Карточка поста - базовые стили */
|
/* Сетка на 3 колонки */
|
||||||
|
.posts-grid-3 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: var(--grid-gap);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Базовый класс для обратной совместимости */
|
||||||
|
.posts-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--grid-gap);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== КАРТОЧКИ ПОСТОВ ===== */
|
||||||
|
|
||||||
.post-card {
|
.post-card {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
@@ -44,16 +62,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 0;
|
height: 0;
|
||||||
padding-bottom: 100%; /* Aspect ratio hack для надежности */
|
padding-bottom: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
/* Большие карточки (десктоп) */
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
.post-card-large {
|
|
||||||
grid-column: span 2;
|
|
||||||
aspect-ratio: 2 / 1;
|
|
||||||
padding-bottom: 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hover эффекты */
|
/* Hover эффекты */
|
||||||
@@ -66,15 +75,6 @@
|
|||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ссылка карточки */
|
|
||||||
.post-card-link {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
display: block;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Контейнер изображения */
|
/* Контейнер изображения */
|
||||||
.post-image-container {
|
.post-image-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -97,8 +97,6 @@
|
|||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Оверлей с контентом */
|
/* Оверлей с контентом */
|
||||||
.post-content-overlay {
|
.post-content-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -118,10 +116,6 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.post-meta-overlay {
|
.post-meta-overlay {
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
@@ -145,11 +139,6 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-badge-white{
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-name {
|
.author-name {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
@@ -157,41 +146,36 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Убедитесь что индикаторы не занимают лишнее место */
|
.author-link {
|
||||||
#infinity-scroll-sentinel {
|
color: white;
|
||||||
height: 1px;
|
text-decoration: none;
|
||||||
width: 100%;
|
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
visibility: hidden;
|
transition: border-color var(--transition-speed) ease;
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-indicator {
|
.author-link:hover {
|
||||||
text-align: center;
|
border-bottom-color: white;
|
||||||
padding: 40px 0;
|
|
||||||
color: #666;
|
|
||||||
min-height: auto; /* ← Важно! */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-more-posts {
|
/* ===== АДАПТИВНОСТЬ ДЛЯ РАЗНЫХ ЭКРАНОВ ===== */
|
||||||
text-align: center;
|
|
||||||
padding: 30px 0;
|
|
||||||
color: #666;
|
|
||||||
font-size: 16px;
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
margin-top: 20px;
|
|
||||||
min-height: auto; /* ← Важно! */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Десктоп (1200px и больше) */
|
||||||
|
|
||||||
|
|
||||||
/* Улучшения для больших карточек */
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
.post-card-large .post-category-badge {
|
/* Для сетки 4 колонки - большие карточки на 2 колонки */
|
||||||
font-size: 12px;
|
.posts-grid-4 .post-card-large {
|
||||||
padding: 6px 12px;
|
grid-column: span 2;
|
||||||
|
aspect-ratio: 2 / 1;
|
||||||
|
padding-bottom: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Для сетки 3 колонки - большие карточки на 2 колонки */
|
||||||
|
.posts-grid-3 .post-card-large {
|
||||||
|
grid-column: span 2;
|
||||||
|
aspect-ratio: 2 / 1;
|
||||||
|
padding-bottom: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Улучшения для больших карточек */
|
||||||
.post-card-large .post-content-overlay {
|
.post-card-large .post-content-overlay {
|
||||||
padding: var(--overlay-padding-large);
|
padding: var(--overlay-padding-large);
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@@ -212,27 +196,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ноутбуки: 3 колонки */
|
/* Ноутбуки (992px - 1199px) */
|
||||||
@media (min-width: 992px) and (max-width: 1199px) {
|
@media (min-width: 992px) and (max-width: 1199px) {
|
||||||
.posts-grid {
|
/* Все сетки переходят на 3 колонки */
|
||||||
|
.posts-grid-4,
|
||||||
|
.posts-grid-3 {
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-card-large {
|
/* Большие карточки становятся обычными */
|
||||||
|
.posts-grid-4 .post-card-large,
|
||||||
|
.posts-grid-3 .post-card-large {
|
||||||
grid-column: span 1;
|
grid-column: span 1;
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
padding-bottom: 100%;
|
padding-bottom: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Планшеты: 2 колонки */
|
/* Планшеты (768px - 991px) */
|
||||||
@media (min-width: 768px) and (max-width: 991px) {
|
@media (min-width: 768px) and (max-width: 991px) {
|
||||||
.posts-grid {
|
/* Все сетки переходят на 2 колонки */
|
||||||
|
.posts-grid-4,
|
||||||
|
.posts-grid-3 {
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: var(--grid-gap-tablet);
|
gap: var(--grid-gap-tablet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-card-large {
|
/* Большие карточки становятся обычными */
|
||||||
|
.posts-grid-4 .post-card-large,
|
||||||
|
.posts-grid-3 .post-card-large {
|
||||||
grid-column: span 1;
|
grid-column: span 1;
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
padding-bottom: 100%;
|
padding-bottom: 100%;
|
||||||
@@ -243,18 +235,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Мобильные: 1 колонка */
|
/* Мобильные (до 767px) */
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.posts-grid {
|
/* Все сетки переходят на 1 колонку */
|
||||||
|
.posts-grid-4,
|
||||||
|
.posts-grid-3 {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: var(--grid-gap-tablet);
|
gap: var(--grid-gap-tablet);
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-card {
|
.post-card,
|
||||||
aspect-ratio: 2 / 1;
|
|
||||||
padding-bottom: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-card-large {
|
.post-card-large {
|
||||||
aspect-ratio: 2 / 1;
|
aspect-ratio: 2 / 1;
|
||||||
padding-bottom: 50%;
|
padding-bottom: 50%;
|
||||||
@@ -275,16 +265,9 @@
|
|||||||
.post-title-overlay {
|
.post-title-overlay {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-category-badge {
|
|
||||||
top: 12px;
|
|
||||||
right: 12px;
|
|
||||||
font-size: 10px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Очень маленькие экраны */
|
/* Очень маленькие экраны (до 480px) */
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.post-card,
|
.post-card,
|
||||||
.post-card-large {
|
.post-card-large {
|
||||||
@@ -300,33 +283,22 @@
|
|||||||
.post-title-overlay {
|
.post-title-overlay {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-category-badge {
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
padding: 3px 6px;
|
|
||||||
font-size: 9px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Вспомогательные классы */
|
/* ===== ИНДИКАТОРЫ ЗАГРУЗКИ ===== */
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
#infinity-scroll-sentinel {
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
height: 1px;
|
||||||
padding: 0;
|
width: 100%;
|
||||||
margin: -1px;
|
visibility: hidden;
|
||||||
overflow: hidden;
|
pointer-events: none;
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap;
|
|
||||||
border: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Состояния загрузки */
|
|
||||||
.loading-indicator {
|
.loading-indicator {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 40px 0;
|
padding: 40px 0;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
min-height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
@@ -352,6 +324,21 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== ВСПОМОГАТЕЛЬНЫЕ КЛАССЫ ===== */
|
||||||
|
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-posts {
|
.no-posts {
|
||||||
@@ -361,6 +348,7 @@
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Мобильные адаптации для индикаторов */
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
|
|||||||
Reference in New Issue
Block a user