Новые мелькие изменения

This commit is contained in:
Web-serfer 2026-04-15 00:44:37 +05:00
parent 9f14235135
commit b407e85846
3 changed files with 66 additions and 50 deletions

View file

@ -0,0 +1 @@
{"version":"1.21.0"}

View file

@ -1,7 +1,7 @@
import type { Component } from 'solid-js';
import { Show } from 'solid-js';
import { FiChevronLeft, FiChevronRight } from 'solid-icons/fi';
import type { PaginationProps } from '@globalInterfaces';
import type { Component } from "solid-js";
import { Show, Match, Switch } from "solid-js";
import { FiChevronLeft, FiChevronRight } from "solid-icons/fi"; // Убедитесь, что иконки установлены
import type { PaginationProps } from "@globalInterfaces";
interface Props {
page: PaginationProps;
@ -17,41 +17,59 @@ const Pagination: Component<Props> = (props) => {
class="mt-12 flex items-center justify-center gap-6 border-t border-neutral-200 pt-8 dark:border-neutral-800"
aria-label="Навигация по страницам"
>
{/* Кнопка "Назад" */}
<a
href={props.page.url.prev}
classList={{
'flex items-center justify-center h-10 w-10 rounded-full border transition-all duration-300': true,
'border-neutral-300 bg-neutral-100 text-neutral-400 cursor-not-allowed dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-600': isPrevDisabled(),
'border-neutral-300 bg-white text-neutral-700 shadow-sm hover:bg-neutral-50 hover:shadow-md active:scale-95 dark:bg-neutral-800 dark:border-neutral-600 dark:text-neutral-300 dark:hover:bg-neutral-700': !isPrevDisabled(),
}}
aria-disabled={isPrevDisabled()}
aria-label="Перейти на предыдущую страницу"
>
<FiChevronLeft class="h-5 w-5" />
</a>
<Switch>
<Match when={isPrevDisabled()}>
<button
disabled
class="flex h-10 w-10 cursor-not-allowed items-center justify-center rounded-full border border-neutral-300 bg-neutral-100 text-neutral-400 transition-all duration-300 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-600"
aria-label="Предыдущая страница недоступна"
>
<FiChevronLeft class="h-5 w-5" />
</button>
</Match>
<Match when={!isPrevDisabled()}>
<a
href={props.page.url.prev}
class="flex h-10 w-10 items-center justify-center rounded-full border border-neutral-300 bg-white text-neutral-700 shadow-sm transition-all duration-300 hover:bg-neutral-50 hover:shadow-md active:scale-95 dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700"
aria-label="Перейти на предыдущую страницу"
>
<FiChevronLeft class="h-5 w-5" />
</a>
</Match>
</Switch>
{/* Индикатор текущей страницы */}
<span class="text-sm font-medium text-neutral-600 dark:text-neutral-400">
Страница <span class="font-semibold text-blue-600 dark:text-blue-400">{props.page.currentPage}</span> из {props.page.lastPage}
Страница{" "}
<span class="font-semibold text-blue-600 dark:text-blue-400">
{props.page.currentPage}
</span>{" "}
из {props.page.lastPage}
</span>
{/* Кнопка "Вперед" */}
<a
href={props.page.url.next}
classList={{
'flex items-center justify-center h-10 w-10 rounded-full border transition-all duration-300': true,
'border-neutral-300 bg-neutral-100 text-neutral-400 cursor-not-allowed dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-600': isNextDisabled(),
'border-neutral-300 bg-white text-neutral-700 shadow-sm hover:bg-neutral-50 hover:shadow-md active:scale-95 dark:bg-neutral-800 dark:border-neutral-600 dark:text-neutral-300 dark:hover:bg-neutral-700': !isNextDisabled(),
}}
aria-disabled={isNextDisabled()}
aria-label="Перейти на следующую страницу"
>
<FiChevronRight class="h-5 w-5" />
</a>
<Switch>
<Match when={isNextDisabled()}>
<button
disabled
class="flex h-10 w-10 cursor-not-allowed items-center justify-center rounded-full border border-neutral-300 bg-neutral-100 text-neutral-400 transition-all duration-300 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-600"
aria-label="Следующая страница недоступна"
>
<FiChevronRight class="h-5 w-5" />
</button>
</Match>
<Match when={!isNextDisabled()}>
<a
href={props.page.url.next}
class="flex h-10 w-10 items-center justify-center rounded-full border border-neutral-300 bg-white text-neutral-700 shadow-sm transition-all duration-300 hover:bg-neutral-50 hover:shadow-md active:scale-95 dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700"
aria-label="Перейти на следующую страницу"
>
<FiChevronRight class="h-5 w-5" />
</a>
</Match>
</Switch>
</nav>
</Show>
);
};
export default Pagination;
export default Pagination;

View file

@ -1,10 +1,10 @@
// src/pages/api/search.json.ts
import type { APIRoute } from "astro";
import { pb } from '@lib/pocketbase';
import { pb } from "@lib/pocketbase";
export const GET: APIRoute = async ({ url }): Promise<Response> => {
try {
const query = url.searchParams.get('q')?.trim() || '';
const query = url.searchParams.get("q")?.trim() || "";
if (!query) {
return new Response(JSON.stringify([]), {
@ -13,19 +13,17 @@ export const GET: APIRoute = async ({ url }): Promise<Response> => {
});
}
// 1. САНИТИЗАЦИЯ: Экранируем кавычки, чтобы запрос не сломал синтаксис фильтра PB
// Если пользователь введет: React "Hero", мы превратим это в: React \"Hero\"
const safeQuery = query.replace(/"/g, '\\"');
// УЛУЧШЕНИЕ: Используем параметризованный фильтр для безопасности и надежности.
// PocketBase SDK автоматически экранирует значения, предотвращая ошибки.
const filterString = `isActive = true && (title ~ {:query} || description ~ {:query} || content ~ {:query})`;
// 2. СБОРКА ФИЛЬТРА: Собираем строку вручную (это самый надежный способ)
// Мы ищем совпадения в заголовке, описании ИЛИ контенте
const filterString = `isActive = true && (title ~ "${safeQuery}" || description ~ "${safeQuery}" || content ~ "${safeQuery}")`;
const result = await pb.collection('posts').getList(1, 15, {
const result = await pb.collection("posts").getList(1, 15, {
filter: filterString,
sort: '-publishDate',
// Передаем значение query в качестве параметра
filterParams: { query },
sort: "-publishDate",
// Запрашиваем только нужные поля (без content, чтобы не грузить сеть)
fields: 'id,title,description,slug',
fields: "id,title,description,slug",
});
const searchData = result.items.map((post) => ({
@ -39,17 +37,16 @@ export const GET: APIRoute = async ({ url }): Promise<Response> => {
status: 200,
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=60"
"Cache-Control": "public, max-age=60",
},
});
} catch (error) {
// Логируем ошибку подробно, чтобы видеть причину в консоли
console.error('Search API error:', error);
console.error("Search API error:", error);
return new Response(JSON.stringify({ error: 'Internal server error' }), {
return new Response(JSON.stringify({ error: "Internal server error" }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
headers: { "Content-Type": "application/json" },
});
}
};
};