This commit is contained in:
2026-07-18 14:52:26 +02:00
parent b8d32de25f
commit db62cee09b
4 changed files with 281 additions and 249 deletions
+31 -5
View File
@@ -6,6 +6,12 @@ $log_msg = "DEBUG search-results.php: search_results count = " . count($_SESSION
echo "<script>console.log(" . json_encode($log_msg) . ");</script>";
$all_results = $_SESSION["search_results"] ?? [];
?>
<!-- Übergabe der rohen PHP-Ergebnisse an JavaScript -->
<script id="search-data-json" type="application/json">
<?php echo json_encode($all_results); ?>
</script>
<?php
$query = $_SESSION["search_query"] ?? "";
$totalResultsCount = count($all_results);
@@ -47,7 +53,7 @@ $resultCount = count($results);
<!-- Links: Seitenleiste für Filter und Suche -->
<aside class="s-res-sidebar">
<!-- Sortierfuntion Box und Such Box-->
<!-- Sortierfuntion Box und Such Box-->
<form action="php/controller/search-results-controller.php" method="GET" id="search-form-id" class="s-res-sidebar-form">
<input type="hidden" id="s-res-page-input" name="page" value="<?php echo $_GET['page'] ?? 1; ?>">
@@ -133,7 +139,7 @@ $resultCount = count($results);
<?php foreach ($results as $item):
// Anzahl der Likes ermitteln (falls es ein Array ist, zählen; falls Zahl, direkt nutzen)
$likesCount = isset($item['likes']) && is_array($item['likes']) ? count($item['likes']) : ($item['likes'] ?? 0);
?>
?>
<div class="s-res-item" data-likes="<?php echo $likesCount; ?>" data-category="<?php echo strtolower($item['category'] ?? ''); ?>">
<div class="s-res-content">
<h2 class="s-res-item-title">
@@ -180,13 +186,33 @@ $resultCount = count($results);
</div>
<div class="s-res-page-navigation">
<button type="button" class="s-res-page-btn" id="prev-page-btn" data-page="0">«</button>
<!-- Vorherige Seite Link -->
<?php if ($currentPage > 1): ?>
<a href="?page=<?php echo $currentPage - 1; ?>&limit=<?php echo $limit; ?>" class="s-res-page-btn" id="prev-page-btn" data-page="<?php echo $currentPage - 1; ?>">«</a>
<?php else: ?>
<span class="s-res-page-btn disabled">«</span>
<?php endif; ?>
<span id="dynamic-page-numbers"></span>
<!-- Fallback-Seitenzahlen für Nutzer OHNE JavaScript -->
<span id="dynamic-page-numbers">
<noscript>
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<a href="?page=<?php echo $i; ?>&limit=<?php echo $limit; ?>" class="s-res-page-btn <?php echo $i === $currentPage ? 'active' : ''; ?>">
<?php echo $i; ?>
</a>
<?php endfor; ?>
</noscript>
</span>
<button type="button" class="s-res-page-btn" id="next-page-btn" data-page="2">»</button>
<!-- Nächste Seite Link -->
<?php if ($currentPage < $totalPages): ?>
<a href="?page=<?php echo $currentPage + 1; ?>&limit=<?php echo $limit; ?>" class="s-res-page-btn" id="next-page-btn" data-page="<?php echo $currentPage + 1; ?>">»</a>
<?php else: ?>
<span class="s-res-page-btn disabled">»</span>
<?php endif; ?>
</div>
</div>
</main>
+1 -1
View File
@@ -68,7 +68,7 @@ if ($pfad === "deleteAccount") {
<script src="js/paginator.js" async></script>
<script src="js/sorter.js" async></script>
<script src="js/comments.js" defer></script>
<!--<script src="js/editor.js" async></script>-->
<script src="js/editor.js" async></script>
<script src="js/filter.js" async></script>
<title>EduForge</title>
+203 -227
View File
@@ -1,242 +1,218 @@
console.log("paginator.js wurde erfolgreich geladen!");
(function () {
'use strict';
// Globale Variablen für den JavaScript-Betrieb
let allItems = [];
let filteredItems = [];
let currentJsPage = 1;
let jsLimit = 10;
const state = {
items: [],
sorted: [],
category: 'all',
sort: 'alphabet',
limit: 10,
page: 1,
};
function initPaginator() {
const dataContainer = document.getElementById('search-data-json');
if (!dataContainer) return;
function loadData() {
const el = document.getElementById('s-res-data');
console.log("s-res-data Element:", el);
if (!el) { console.error('s-res-data Element nicht gefunden'); return []; }
const raw = el.textContent.trim();
console.log("Roher Inhalt (erste 500 Zeichen):", raw.slice(0, 500));
console.log("Roher Inhalt Länge:", raw.length);
if (!raw) { console.error('s-res-data ist leer'); return []; }
try {
const parsed = JSON.parse(raw);
console.log("Geparst, Anzahl Elemente:", parsed.length);
return parsed;
} catch (e) {
console.error('JSON.parse fehlgeschlagen:', e, raw.slice(0, 200));
return [];
}
// 1. Daten aus PHP-JSON-Schnittstelle laden
try {
allItems = JSON.parse(dataContainer.textContent);
} catch (e) {
console.error("Fehler beim Laden der Suchdaten:", e);
return;
}
function applyFilterAndSort() {
let list = state.items.slice();
// 2. Event-Listener für Live-Interaktionen im Browser registrieren
const sortRadios = document.querySelectorAll('.sort-radio');
const categorySelect = document.getElementById('category-filter');
const limitSelect = document.getElementById('s-res-per-page');
if (state.category && state.category !== 'all') {
list = list.filter(item => (item.category || '') === state.category);
}
switch (state.sort) {
case 'likes':
list.sort((a, b) => (b.likes || 0) - (a.likes || 0));
break;
case 'newest':
list.sort((a, b) => (b.creationDate || '').localeCompare(a.creationDate || ''));
break;
case 'oldest':
list.sort((a, b) => (a.creationDate || '').localeCompare(b.creationDate || ''));
break;
default:
list.sort((a, b) => (a.title || '').localeCompare(b.title || '', 'de', { sensitivity: 'base' }));
}
state.sorted = list;
const totalPages = Math.max(1, Math.ceil(state.sorted.length / state.limit));
state.page = Math.min(Math.max(state.page, 1), totalPages);
}
function el(tag, className, text) {
const e = document.createElement(tag);
if (className) e.className = className;
if (text !== undefined) e.textContent = text;
return e;
}
function renderList() {
const container = document.querySelector('.s-res-list');
if (!container) return;
const start = (state.page - 1) * state.limit;
const pageItems = state.sorted.slice(start, start + state.limit);
container.innerHTML = '';
if (pageItems.length === 0) {
container.appendChild(el('p', null, 'Keine Beiträge zu diesem Suchbegriff gefunden.'));
return;
}
pageItems.forEach(item => {
const item_el = el('div', 's-res-item');
item_el.dataset.likes = item.likes;
item_el.dataset.category = item.category;
const link = el('a', 's-res-link', item.title);
link.href = 'index.php?pfad=showArticle&id=' + encodeURIComponent(item.id);
const title = el('h2', 's-res-item-title');
title.appendChild(link);
const authorName = el('span', 's-res-author-name', item.author);
const author = el('p', 's-res-author', 'Von: ');
author.appendChild(authorName);
const likes = el('span', 's-res-likes', '❤️ ' + item.likes);
const metaRow = el('div', 's-res-meta-row');
metaRow.appendChild(author);
metaRow.appendChild(likes);
const content = el('div', 's-res-content');
content.appendChild(title);
content.appendChild(metaRow);
item_el.appendChild(content);
item_el.appendChild(el('div', 's-res-arrow', '→'));
container.appendChild(item_el);
sortRadios.forEach(radio => radio.addEventListener('change', applyFiltersAndRender));
if (categorySelect) categorySelect.addEventListener('change', applyFiltersAndRender);
if (limitSelect) {
limitSelect.addEventListener('change', function() {
jsLimit = parseInt(this.value) || 10;
currentJsPage = 1; // Zurück auf Seite 1 bei Limit-Wechsel
applyFiltersAndRender();
});
}
function renderMeta() {
const metaEl = document.querySelector('.s-res-meta');
const queryInput = document.getElementById('site-search');
if (metaEl) {
const q = queryInput ? queryInput.value : '';
metaEl.textContent = state.sorted.length + ' Treffer für Ihre Suchanfrage "' + q + '"';
}
}
// Pfeil-Navigation (Vor / Zurück) abfangen
const prevBtn = document.getElementById('prev-page-btn');
const nextBtn = document.getElementById('next-page-btn');
function renderPagination() {
const nav = document.querySelector('.s-res-page-navigation');
if (!nav) return;
const totalPages = Math.max(1, Math.ceil(state.sorted.length / state.limit));
nav.innerHTML = '';
const makeBtn = (label, page, disabled, active) => {
const btn = el('button', 's-res-page-btn' + (active ? ' is-active' : ''), label);
btn.type = 'button';
btn.disabled = !!disabled;
btn.addEventListener('click', () => goToPage(page));
return btn;
};
nav.appendChild(makeBtn('«', state.page - 1, state.page <= 1));
const windowSize = 2;
for (let p = 1; p <= totalPages; p++) {
const edge = p === 1 || p === totalPages;
const near = Math.abs(p - state.page) <= windowSize;
if (!edge && !near) {
if (p === 2 || p === totalPages - 1) nav.appendChild(el('span', 's-res-page-dots', '…'));
continue;
if (prevBtn) {
prevBtn.addEventListener('click', function(e) {
e.preventDefault();
if (currentJsPage > 1) {
currentJsPage--;
renderListAndPagination();
}
nav.appendChild(makeBtn(String(p), p, false, p === state.page));
}
nav.appendChild(makeBtn('»', state.page + 1, state.page >= totalPages));
}
function updateUrl() {
const url = new URL(window.location.href);
url.searchParams.set('sort', state.sort);
url.searchParams.set('category', state.category);
url.searchParams.set('limit', state.limit);
url.searchParams.set('page', state.page);
window.history.replaceState(null, '', url);
}
function rerender() {
applyFilterAndSort();
renderList();
renderMeta();
renderPagination();
updateUrl();
}
function goToPage(page) {
const totalPages = Math.max(1, Math.ceil(state.sorted.length / state.limit));
state.page = Math.min(Math.max(page, 1), totalPages);
renderList();
renderPagination();
updateUrl();
}
function initPaginator() {
console.log("initPaginator gestartet");
state.items = loadData();
console.log("Geladene Items:", state.items);
const checkedSort = document.querySelector('.sort-radio:checked');
console.log("checkedSort:", checkedSort);
state.sort = checkedSort ? checkedSort.value : 'alphabet';
const categorySelect = document.getElementById('category-filter');
console.log("categorySelect:", categorySelect);
state.category = categorySelect ? categorySelect.value.toLowerCase() : 'all';
const limitSelect = document.getElementById('s-res-per-page');
console.log("limitSelect:", limitSelect);
state.limit = limitSelect ? parseInt(limitSelect.value, 10) || 10 : 10;
const params = new URLSearchParams(window.location.search);
state.page = parseInt(params.get('page') || '1', 10) || 1;
document.querySelectorAll('.sort-radio').forEach(radio => {
radio.addEventListener('change', function () {
state.sort = this.value;
state.page = 1;
rerender();
});
});
if (categorySelect) {
categorySelect.addEventListener('change', function () {
state.category = this.value.toLowerCase();
state.page = 1;
rerender();
});
}
if (limitSelect) {
limitSelect.addEventListener('change', function () {
state.limit = parseInt(this.value, 10) || 10;
state.page = 1;
rerender();
});
}
// "Filter anwenden"-Button wird bei aktivem JS nicht gebraucht
const applyBtn = document.querySelector('.s-res-apply-btn');
if (applyBtn) applyBtn.style.display = 'none';
// Server-gerenderte Pagination-Links durch clientseitige Steuerung ersetzen
document.querySelectorAll('.s-res-page-navigation .s-res-page-btn').forEach(link => {
link.addEventListener('click', function (evt) {
evt.preventDefault();
const target = parseInt(this.getAttribute('data-page'), 10);
if (!isNaN(target)) goToPage(target);
});
});
rerender();
console.log("initPaginator abgeschlossen, State:", state);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPaginator);
if (nextBtn) {
nextBtn.addEventListener('click', function(e) {
e.preventDefault();
const totalPages = Math.max(1, Math.ceil(filteredItems.length / jsLimit));
if (currentJsPage < totalPages) {
currentJsPage++;
renderListAndPagination();
}
});
}
// Initialer Start: Daten verarbeiten und anzeigen
// Liest das vom Server übergebene Limit aus
if (limitSelect) jsLimit = parseInt(limitSelect.value) || 10;
applyFiltersAndRender();
}
// Verarbeitet Sortierung + Filterung komplett im Browser (Serverentlastung)
function applyFiltersAndRender() {
// KATEGORIE FILTERN
const categorySelect = document.getElementById('category-filter');
const activeCategory = categorySelect ? categorySelect.value : 'all';
if (activeCategory === 'all') {
filteredItems = [...allItems];
} else {
initPaginator();
filteredItems = allItems.filter(item =>
item.category && item.category.toLowerCase() === activeCategory.toLowerCase()
);
}
})();
// SORTIERUNG ANWENDEN
const activeSortRadio = document.querySelector('.sort-radio:checked');
const sortStyle = activeSortRadio ? activeSortRadio.value : 'alphabet';
if (sortStyle === 'alphabet') {
filteredItems.sort((a, b) => (a.title || '').localeCompare(b.title || '', 'de', { sensitivity: 'base' }));
} else if (sortStyle === 'likes') {
filteredItems.sort((a, b) => {
const countA = Array.isArray(a.likes) ? a.likes.length : (parseInt(a.likes) || 0);
const countB = Array.isArray(b.likes) ? b.likes.length : (parseInt(b.likes) || 0);
return countB - countA;
});
} else if (sortStyle === 'newest') {
filteredItems.sort((a, b) => (b.creationDate || '').localeCompare(a.creationDate || ''));
} else if (sortStyle === 'oldest') {
filteredItems.sort((a, b) => (a.creationDate || '').localeCompare(b.creationDate || ''));
}
// Nach dem Filtern/Sortieren springen wir zurück auf Seite 1
currentJsPage = 1;
renderListAndPagination();
}
// HTML-Listenansicht und Paginierungs-Zahlen neu aufbauen
function renderListAndPagination() {
const listContainer = document.querySelector('.s-res-list');
const pageNumbersContainer = document.getElementById('dynamic-page-numbers');
if (!listContainer) return;
// Gesamtseitenzahl berechnen
const totalPages = Math.max(1, Math.ceil(filteredItems.length / jsLimit));
if (currentJsPage > totalPages) currentJsPage = totalPages;
// Meta-Text aktualisieren (Trefferanzeige)
const metaText = document.querySelector('.s-res-meta');
if (metaText) {
const queryText = metaText.textContent.split('"')[1] || '';
metaText.innerHTML = `${filteredItems.length} Treffer für Ihre Suchanfrage "${queryText}"`;
}
// 1. ERGEBNISLISTE IM DOM REINJECKTEN
listContainer.innerHTML = ''; // Vorherige Ergebnisse leeren
if (filteredItems.length === 0) {
listContainer.innerHTML = '<p>Keine Beiträge zu diesem Suchbegriff gefunden.</p>';
if (pageNumbersContainer) pageNumbersContainer.innerHTML = '';
updateArrowButtons(totalPages);
return;
}
// Ausschnitt für die aktuelle Seite berechnen (Offset)
const offset = (currentJsPage - 1) * jsLimit;
const pageItems = filteredItems.slice(offset, offset + jsLimit);
// HTML für die Zeilen generieren
pageItems.forEach(item => {
const likesCount = Array.isArray(item.likes) ? item.likes.length : (parseInt(item.likes) || 0);
const itemHtml = `
<div class="s-res-item" data-likes="${likesCount}" data-category="${(item.category || '').toLowerCase()}">
<div class="s-res-content">
<h2 class="s-res-item-title">
<a href="index.php?pfad=showArticle&id=${item.id}" class="s-res-link">${escapeHtml(item.title)}</a>
</h2>
<div class="s-res-meta-row">
<p class="s-res-author">Von: <span class="s-res-author-name">${escapeHtml(item.author)}</span></p>
<span class="s-res-likes">❤️ ${likesCount}</span>
</div>
</div>
<div class="s-res-arrow">&rarr;</div>
</div>
`;
listContainer.insertAdjacentHTML('beforeend', itemHtml);
});
// 2. DYNAMISCHE SEITENZAHLEN GENERIEREN
if (pageNumbersContainer) {
pageNumbersContainer.innerHTML = ''; // Vorherige Zahlen leeren
// Erzeugt Klickbare Seitenzahlen im JS-Modus
for (let i = 1; i <= totalPages; i++) {
const pageBtn = document.createElement('button');
pageBtn.type = 'button';
pageBtn.className = `s-res-page-btn ${i === currentJsPage ? 'active' : ''}`;
pageBtn.innerText = i;
pageBtn.addEventListener('click', function() {
currentJsPage = i;
renderListAndPagination();
});
pageNumbersContainer.appendChild(pageBtn);
}
}
updateArrowButtons(totalPages);
}
// Steuert die Aktivierung/Deaktivierung der Vor- und Zurück-Pfeile
function updateArrowButtons(totalPages) {
const prevBtn = document.getElementById('prev-page-btn');
const nextBtn = document.getElementById('next-page-btn');
if (prevBtn) {
if (currentJsPage <= 1) {
prevBtn.classList.add('disabled');
prevBtn.style.pointerEvents = 'none';
} else {
prevBtn.classList.remove('disabled');
prevBtn.style.pointerEvents = 'auto';
}
}
if (nextBtn) {
if (currentJsPage >= totalPages) {
nextBtn.classList.add('disabled');
nextBtn.style.pointerEvents = 'none';
} else {
nextBtn.classList.remove('disabled');
nextBtn.style.pointerEvents = 'auto';
}
}
}
// Hilfsfunktion gegen XSS-Injections im Browser
function escapeHtml(text) {
if (!text) return '';
return text.toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
// DOM-Ready Check
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initPaginator);
} else {
initPaginator();
}
+37 -7
View File
@@ -16,11 +16,37 @@ if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) {
$_SESSION["message"] = "invalid_search_query";
} else {
try {
$articleManager = ArticleManager::getInstance();
$userManager = UserManager::getInstance();
$results = $articleManager->search($search);
$sortStyle = $_GET['sort'] ?? 'alphabet';
$_SESSION['search_sort'] = $sortStyle;
if ($sortStyle === 'alphabet') {
// Titel aufsteigend alphabetiisch sortiert
usort($results, function ($a, $b) {
return strcasecmp($a->getTitle(), $b->getTitle());
});
} elseif ($sortStyle === 'likes') {
usort($results, function($a, $b) {
return $b->getLikeCount() <=> $a->getLikeCount();
});
} elseif ($sortStyle === 'newest') {
// Datum neu zu alt sortiert
usort($results, function($a, $b) {
return strcmp($b->getCreationDate(), $a->getCreationDate());
});
} elseif ($sortStyle === 'oldest') {
// Datum alt zu neu sortiert
usort($results, function($a, $b) {
return strcmp($a->getCreationDate(), $b->getCreationDate());
});
}
// Ergebnisse werden in ein flaches array umgewandelt, da sont incomplete-PHP error im Ergebnis
$safeArrayResults = [];
foreach ($results as $obj) {
$safeArrayResults[] = [
@@ -39,16 +65,20 @@ if ($_SERVER["REQUEST_METHOD"] === "GET" && isset($_GET["q"])) {
$_SESSION["search_query"] = $search;
$_SESSION["message"] = "new_search_results";
// Bei einer neuen Suche Anzeige-Filter zurücksetzen
$_SESSION['search_sort'] = 'alphabet';
$_SESSION['search_category'] = 'all';
$_SESSION['search_limit'] = 10;
} catch (Exception $e) {
} catch (Exception $e){
$_SESSION["message"] = "internal_error";
}
}
header("Location: ../../index.php?pfad=search-results&q=" . urlencode($search));
$sort = $_GET['sort'] ?? 'alphabet';
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 10;
if (!searchLimitValidator($limit)) {
$limit = 10;
}
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
header("Location: ../../index.php?pfad=search-results&q=" . urlencode($search) . "&sort=" . urlencode($sort) . "&limit=" . $limit . "&page=" . $page);
exit();
}
?>