diff --git a/content/profile.php b/content/profile.php
index 3109587..ef6bdf6 100644
--- a/content/profile.php
+++ b/content/profile.php
@@ -1,5 +1,4 @@
Treffer für Ihre Suchanfrage ""
+
diff --git a/content/showArticle.php b/content/showArticle.php
index 71cea81..8f43bd9 100644
--- a/content/showArticle.php
+++ b/content/showArticle.php
@@ -510,6 +510,18 @@ if ($replyAuthor === null) {
+
+
+
+
+
+ Kategoriebeiträge werden geladen …
+
+
diff --git a/css/main.css b/css/main.css
index a2bc707..a9dc026 100644
--- a/css/main.css
+++ b/css/main.css
@@ -414,4 +414,34 @@ h1 {
/* Geändertes Symbol, wenn der Bereich geöffnet ist */
.edit-comment-details[open] .edit-comment-button::before {
content: "▲ ";
+}
+.comment-loading {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-top: 10px;
+}
+
+.comment-loading[hidden] {
+ display: none;
+}
+
+.comment-spinner {
+ width: 16px;
+ height: 16px;
+ border: 2px solid #cbd5e1;
+ border-top-color: #1f2937;
+ border-radius: 50%;
+ animation: comment-spinner-rotation 0.8s linear infinite;
+}
+
+@keyframes comment-spinner-rotation {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+#comment-form button[type="submit"]:disabled {
+ cursor: wait;
+ opacity: 0.65;
}
\ No newline at end of file
diff --git a/css/search-results.css b/css/search-results.css
index a89817c..fbf630a 100644
--- a/css/search-results.css
+++ b/css/search-results.css
@@ -271,4 +271,33 @@ CSS für die Suchergebnis-Seite
align-items: flex-start;
}
}
+/* Ladeanzeige beim Nachladen von Such- und Kategorieergebnissen */
+.results-loading {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin: 15px 0;
+ color: #4a5568;
+ font-size: 0.95rem;
+}
+
+.results-loading[hidden] {
+ display: none;
+}
+
+.results-spinner {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+ border: 2px solid #cbd5e1;
+ border-top-color: #3182ce;
+ border-radius: 50%;
+ animation: results-spinner-rotation 0.8s linear infinite;
+}
+
+@keyframes results-spinner-rotation {
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/js/comments.js b/js/comments.js
index 9ad43a8..5760cf5 100644
--- a/js/comments.js
+++ b/js/comments.js
@@ -11,7 +11,19 @@ document.addEventListener("DOMContentLoaded", function () {
const parentCommentInput = document.getElementById("parent-comment-id");
const replyInfo = document.getElementById("reply-info");
- if (!form || !commentsList || !commentContent || !parentCommentInput) {
+ const submitButton = form
+ ? form.querySelector('button[type="submit"]')
+ : null;
+
+ const loadingMessage = document.getElementById("comment-loading");
+
+ if (
+ !form
+ || !commentsList
+ || !commentContent
+ || !parentCommentInput
+ || !submitButton
+ ) {
return;
}
@@ -53,6 +65,23 @@ document.addEventListener("DOMContentLoaded", function () {
form.addEventListener("submit", function (event) {
event.preventDefault();
+ /*
+ * Verhindert einen erneuten Submit, während der vorherige
+ * Kommentar noch gespeichert wird.
+ */
+ if (submitButton.disabled) {
+ return;
+ }
+
+ const originalButtonText = submitButton.textContent;
+
+ submitButton.disabled = true;
+ submitButton.textContent = "Wird gesendet …";
+
+ if (loadingMessage) {
+ loadingMessage.hidden = false;
+ }
+
const formData = new FormData(form);
const parentCommentId = parentCommentInput.value;
@@ -293,6 +322,14 @@ document.addEventListener("DOMContentLoaded", function () {
.catch(function (error) {
console.error(error);
alert("Kommentar konnte nicht gesendet werden.");
+ })
+ .finally(function () {
+ submitButton.disabled = false;
+ submitButton.textContent = originalButtonText;
+
+ if (loadingMessage) {
+ loadingMessage.hidden = true;
+ }
});
});
diff --git a/js/search-results.js b/js/search-results.js
index 9461e39..4d12c06 100644
--- a/js/search-results.js
+++ b/js/search-results.js
@@ -25,6 +25,7 @@
let listContainer, sortRadios, categorySelect, limitSelect;
let prevBtn, nextBtn, numbersContainer, jsNav, resultCountEl;
+ let loadingElement;
function init() {
listContainer = document.querySelector('.s-res-list');
@@ -38,23 +39,41 @@
numbersContainer = document.getElementById('dynamic-page-numbers');
jsNav = document.getElementById('js-page-navigation');
resultCountEl = document.getElementById('s-res-result-count');
+ loadingElement = document.getElementById('results-loading');
const checkedRadio = document.querySelector('.sort-radio:checked');
state.sort = checkedRadio ? checkedRadio.value : 'alphabet';
state.category = categorySelect ? categorySelect.value : 'all';
state.itemsPerPage = limitSelect ? (parseInt(limitSelect.value, 10) || 10) : 10;
+ if (loadingElement) {
+ loadingElement.hidden = false;
+ }
+
fetchFullDataset()
.then(function (data) {
state.allItems = data.results || [];
attachEvents();
render();
- if (jsNav) jsNav.style.display = 'flex';
+
+ if (jsNav) {
+ jsNav.style.display = 'flex';
+ }
})
.catch(function (err) {
- // JSON-Endpoint nicht erreichbar: die serverseitig gerenderte
- // (No-JS-)Ansicht bleibt sichtbar und funktioniert weiter.
- console.error('Suchergebnisse konnten nicht nachgeladen werden:', err);
+ /*
+ * Falls das Nachladen nicht funktioniert, bleibt die bereits
+ * serverseitig ausgegebene Ergebnisliste sichtbar.
+ */
+ console.error(
+ 'Suchergebnisse konnten nicht nachgeladen werden:',
+ err
+ );
+ })
+ .finally(function () {
+ if (loadingElement) {
+ loadingElement.hidden = true;
+ }
});
}
diff --git a/js/showCategory.js b/js/showCategory.js
index 08b07ff..394a721 100644
--- a/js/showCategory.js
+++ b/js/showCategory.js
@@ -21,6 +21,7 @@
let listContainer, sortRadios, searchInput, limitSelect;
let prevBtn, nextBtn, numbersContainer, jsNav, resultCountEl;
+ let loadingElement;
let searchDebounceTimer;
function init() {
@@ -35,23 +36,41 @@
numbersContainer = document.getElementById('dynamic-page-numbers');
jsNav = document.getElementById('js-page-navigation');
resultCountEl = document.getElementById('s-res-result-count');
+ loadingElement = document.getElementById('results-loading');
const checkedRadio = document.querySelector('.sort-radio:checked');
state.sort = checkedRadio ? checkedRadio.value : 'alphabet';
state.query = searchInput ? searchInput.value : '';
state.itemsPerPage = limitSelect ? (parseInt(limitSelect.value, 10) || 10) : 10;
+ if (loadingElement) {
+ loadingElement.hidden = false;
+ }
+
fetchFullDataset()
.then(function (data) {
state.allItems = data.results || [];
attachEvents();
render();
- if (jsNav) jsNav.style.display = 'flex';
+
+ if (jsNav) {
+ jsNav.style.display = 'flex';
+ }
})
.catch(function (err) {
- // JSON-Endpoint nicht erreichbar: die serverseitig gerenderte
- // (No-JS-)Ansicht bleibt sichtbar und funktioniert weiter.
- console.error('Kategorie-Beiträge konnten nicht nachgeladen werden:', err);
+ /*
+ * Falls das Nachladen nicht funktioniert, bleibt die bereits
+ * serverseitig ausgegebene Kategorieansicht sichtbar.
+ */
+ console.error(
+ 'Kategorie-Beiträge konnten nicht nachgeladen werden:',
+ err
+ );
+ })
+ .finally(function () {
+ if (loadingElement) {
+ loadingElement.hidden = true;
+ }
});
}
diff --git a/php/controller/index-controller.php b/php/controller/index-controller.php
index e782797..8763069 100644
--- a/php/controller/index-controller.php
+++ b/php/controller/index-controller.php
@@ -20,6 +20,8 @@ if ($pfad === "login") {
include_once "php/controller/confirm-register-controller.php";
} elseif ($pfad === "confirm-password") {
include_once "php/controller/confirm-password-controller.php";
+} elseif ($pfad === "profile") {
+ include_once "php/controller/profile-controller.php";
} elseif ($pfad === "updateComment") {
include_once "php/controller/updateComment-controller.php";
} elseif($pfad === "deleteComment") {
diff --git a/php/model/DatabaseArticleManager.php b/php/model/DatabaseArticleManager.php
index 8e3f57e..ae44b48 100644
--- a/php/model/DatabaseArticleManager.php
+++ b/php/model/DatabaseArticleManager.php
@@ -63,9 +63,12 @@ class DatabaseArticleManager implements ArticleManagerDAO {
VALUES (:title, :content, :author, :category, :tags);";
$command = $db->prepare($sql);
+ if (!$command) {
+ throw new InternalServerErrorException("internal_error");
+ }
// Verknüpft die übergebenen Parameter exakt mit den SQL-Platzhaltern
- $command->execute([
+ $success = $command->execute([
":title" => $title,
":content" => $content,
":author" => $author,
@@ -73,6 +76,10 @@ class DatabaseArticleManager implements ArticleManagerDAO {
":tags" => $tags
]);
+ if (!$success) {
+ throw new InternalServerErrorException("internal_error");
+ }
+
return intval($db->lastInsertId());
} catch (PDOException $e) {
@@ -287,7 +294,7 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$command = $db->prepare($sql);
- // Wildcards für die Suche hinzufügen
+ // Wildcards für die SQL-Suche hinzufügen
$searchParam = '%' . $cleankeyword . '%';
$command->execute([