Новые мелькие изменения
This commit is contained in:
parent
9f14235135
commit
b407e85846
3 changed files with 66 additions and 50 deletions
1
.genkit/traces_idx/genkit.metadata
Normal file
1
.genkit/traces_idx/genkit.metadata
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":"1.21.0"}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue