Новое измнений в код
This commit is contained in:
parent
690a2ab472
commit
735146fbfb
3 changed files with 152 additions and 88 deletions
|
|
@ -8,8 +8,10 @@ import SidebarCategories from '@components/blog/SidebarCategories.astro';
|
||||||
import TagCloud from '@components/blog/TagCloud.astro';
|
import TagCloud from '@components/blog/TagCloud.astro';
|
||||||
import { MONTHS } from '@lib/constants';
|
import { MONTHS } from '@lib/constants';
|
||||||
|
|
||||||
|
// Конфигурация
|
||||||
const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || "http://localhost:8090";
|
const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || "http://localhost:8090";
|
||||||
|
|
||||||
|
// Типы
|
||||||
interface PostRecord {
|
interface PostRecord {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -29,22 +31,7 @@ interface CategoryItem {
|
||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем параметр категории из URL
|
interface Post {
|
||||||
const { category } = Astro.params;
|
|
||||||
|
|
||||||
// Декодируем категорию из URL (например, "ugolovnoe-pravo" -> "Уголовное право")
|
|
||||||
function decodeCategory(slug: string | undefined): string {
|
|
||||||
if (!slug) return "";
|
|
||||||
// Заменяем дефисы на пробелы и приводим к правильному регистру
|
|
||||||
return slug
|
|
||||||
.replace(/-/g, ' ')
|
|
||||||
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
const categoryName = decodeCategory(category);
|
|
||||||
|
|
||||||
// Загружаем посты из PocketBase с фильтром по категории
|
|
||||||
let posts: Array<{
|
|
||||||
id: string;
|
id: string;
|
||||||
image: string;
|
image: string;
|
||||||
category: string;
|
category: string;
|
||||||
|
|
@ -52,43 +39,134 @@ let posts: Array<{
|
||||||
excerpt: string;
|
excerpt: string;
|
||||||
date: string;
|
date: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
}> = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Фильтруем по полю category
|
|
||||||
const filter = encodeURIComponent(`category="${categoryName}"`);
|
|
||||||
const response = await fetch(`${POCKETBASE_URL}/api/collections/posts/records?filter=${filter}&perPage=20&sort=-created`);
|
|
||||||
const data: PocketBaseResponse = await response.json();
|
|
||||||
|
|
||||||
if (data.items) {
|
|
||||||
posts = data.items.map((post) => {
|
|
||||||
const date = new Date(post.created);
|
|
||||||
const formattedDate = `${date.getDate()} ${MONTHS[date.getMonth()]} ${date.getFullYear()} года`;
|
|
||||||
|
|
||||||
// Формируем URL изображения
|
|
||||||
const imageUrl = post.image
|
|
||||||
? `${POCKETBASE_URL}/api/files/posts/${post.id}/${post.image}`
|
|
||||||
: "https://images.unsplash.com/photo-1556761175-5973dc0f32e7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80";
|
|
||||||
|
|
||||||
// Берём категорию из поля category
|
|
||||||
const postCategory = post.category && post.category.trim() !== "" ? post.category : "НОВОСТИ";
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: post.id,
|
|
||||||
image: imageUrl,
|
|
||||||
category: postCategory,
|
|
||||||
title: post.title,
|
|
||||||
excerpt: post.excerpt,
|
|
||||||
date: formattedDate,
|
|
||||||
slug: `/blog/${post.slug}`
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching posts from PocketBase:", error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const popularPosts = [
|
interface PopularPost {
|
||||||
|
image: string;
|
||||||
|
title: string;
|
||||||
|
views: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем параметр из URL
|
||||||
|
const { category } = Astro.params;
|
||||||
|
|
||||||
|
// Декодируем slug в название категории
|
||||||
|
function decodeCategory(slug: string | undefined): string {
|
||||||
|
if (!slug) return "";
|
||||||
|
return slug
|
||||||
|
.replace(/-/g, ' ')
|
||||||
|
.replace(/\b\w/g, (char) => char.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
const categoryName = decodeCategory(category);
|
||||||
|
|
||||||
|
// Форматирование даты
|
||||||
|
function formatDate(dateString: string): string {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return `${date.getDate()} ${MONTHS[date.getMonth()]} ${date.getFullYear()} года`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получение URL изображения
|
||||||
|
function getImageUrl(post: PostRecord): string {
|
||||||
|
if (post.image) {
|
||||||
|
return `${POCKETBASE_URL}/api/files/posts/${post.id}/${post.image}`;
|
||||||
|
}
|
||||||
|
return "https://images.unsplash.com/photo-1556761175-5973dc0f32e7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получение категории поста
|
||||||
|
function getPostCategory(post: PostRecord): string {
|
||||||
|
return post.category?.trim() || "НОВОСТИ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загрузка постов по категории
|
||||||
|
async function fetchPostsByCategory(category: string): Promise<Post[]> {
|
||||||
|
if (!category) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filter = encodeURIComponent(`category="${category}"`);
|
||||||
|
const url = `${POCKETBASE_URL}/api/collections/posts/records?filter=${filter}&perPage=20&sort=-created`;
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: PocketBaseResponse = await response.json();
|
||||||
|
|
||||||
|
if (!data.items) return [];
|
||||||
|
|
||||||
|
return data.items.map((post) => ({
|
||||||
|
id: post.id,
|
||||||
|
image: getImageUrl(post),
|
||||||
|
category: getPostCategory(post),
|
||||||
|
title: post.title,
|
||||||
|
excerpt: post.excerpt,
|
||||||
|
date: formatDate(post.created),
|
||||||
|
slug: `/blog/${post.slug}`
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[CategoryPage] Error fetching posts:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загрузка всех категорий
|
||||||
|
async function fetchCategories(): Promise<CategoryItem[]> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${POCKETBASE_URL}/api/collections/posts/records?perPage=500`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: PocketBaseResponse = await response.json();
|
||||||
|
|
||||||
|
if (!data.items) return [];
|
||||||
|
|
||||||
|
const categoryMap = new Map<string, number>();
|
||||||
|
|
||||||
|
data.items.forEach((post) => {
|
||||||
|
const cat = getPostCategory(post);
|
||||||
|
categoryMap.set(cat, (categoryMap.get(cat) || 0) + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(categoryMap.entries())
|
||||||
|
.map(([name, count]) => ({ name, count }))
|
||||||
|
.sort((a, b) => b.count - a.count)
|
||||||
|
.slice(0, 6);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[CategoryPage] Error fetching categories:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Склонение слова "статья"
|
||||||
|
function getArticlesCountText(count: number): string {
|
||||||
|
const lastDigit = count % 10;
|
||||||
|
const lastTwoDigits = count % 100;
|
||||||
|
|
||||||
|
if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
|
||||||
|
return `${count} статей`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDigit === 1) {
|
||||||
|
return `${count} статья`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDigit >= 2 && lastDigit <= 4) {
|
||||||
|
return `${count} статьи`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${count} статей`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Данные страницы
|
||||||
|
const posts = await fetchPostsByCategory(categoryName);
|
||||||
|
const categories = await fetchCategories();
|
||||||
|
|
||||||
|
const popularPosts: PopularPost[] = [
|
||||||
{
|
{
|
||||||
image: "https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=200&h=200&fit=crop&q=80",
|
image: "https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=200&h=200&fit=crop&q=80",
|
||||||
title: "Как обжаловать решение суда первой инстанции",
|
title: "Как обжаловать решение суда первой инстанции",
|
||||||
|
|
@ -106,35 +184,11 @@ const popularPosts = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Загружаем категории для сайдбара
|
// SEO
|
||||||
let categories: CategoryItem[] = [];
|
const pageTitle = categoryName ? `${categoryName} — Статьи и публикации` : "Категории — Блог";
|
||||||
|
const pageDescription = categoryName
|
||||||
try {
|
? `Все статьи категории "${categoryName}" на сайте адвоката`
|
||||||
const allPostsResponse = await fetch(`${POCKETBASE_URL}/api/collections/posts/records?perPage=500`);
|
: "Все категории статей на сайте адвоката";
|
||||||
const allPostsData: PocketBaseResponse = await allPostsResponse.json();
|
|
||||||
|
|
||||||
if (allPostsData.items) {
|
|
||||||
const categoryMap = new Map<string, number>();
|
|
||||||
|
|
||||||
allPostsData.items.forEach((post) => {
|
|
||||||
const cat = post.category && post.category.trim() !== "" ? post.category : "НОВОСТИ";
|
|
||||||
const currentCount = categoryMap.get(cat) || 0;
|
|
||||||
categoryMap.set(cat, currentCount + 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
categories = Array.from(categoryMap.entries())
|
|
||||||
.map(([name, count]) => ({ name, count }))
|
|
||||||
.sort((a, b) => b.count - a.count)
|
|
||||||
.slice(0, 6);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching categories from PocketBase:", error);
|
|
||||||
categories = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// SEO данные
|
|
||||||
const pageTitle = `${categoryName} — Статьи и публикации`;
|
|
||||||
const pageDescription = `Все статьи категории "${categoryName}" на сайте адвоката`;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ import Toast from "@components/base/Toast.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
title={HOME_PAGE_SEO.title}
|
title={HOME_PAGE_SEO.title}
|
||||||
description={HOME_PAGE_SEO.description}
|
description={HOME_PAGE_SEO.description}
|
||||||
pagePath=""
|
pagePath=""
|
||||||
>
|
>
|
||||||
<Hero />
|
<Hero />
|
||||||
<Stats />
|
<Stats />
|
||||||
|
|
@ -27,21 +27,24 @@ import Toast from "@components/base/Toast.astro";
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const anchors = document.querySelectorAll('a[href^="#"]');
|
const anchors = document.querySelectorAll<HTMLAnchorElement>('a[href^="#"]');
|
||||||
|
|
||||||
anchors.forEach((anchor) => {
|
anchors.forEach((anchor) => {
|
||||||
anchor.addEventListener("click", function (e) {
|
anchor.addEventListener("click", function (this: HTMLAnchorElement, e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const targetId = this.getAttribute("href");
|
const targetId = this.getAttribute("href");
|
||||||
const targetElement = document.querySelector(targetId);
|
if (!targetId) return;
|
||||||
|
|
||||||
|
const targetElement = document.querySelector<HTMLElement>(targetId);
|
||||||
|
|
||||||
if (targetElement) {
|
if (targetElement) {
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
top: targetElement.offsetTop - 80, // отступ сверху для компенсации фиксированного хедера
|
top: targetElement.offsetTop - 80,
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
7
frontend/src/types/global.d.ts
vendored
Normal file
7
frontend/src/types/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
showToast: (message: string, type?: 'success' | 'error' | 'info', duration?: number) => void;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue