erweiterter Beitragseditor
content ist nun im json-Format Bilder können hochgeladen werden Textblöcke können im Editor angehangen werden
This commit is contained in:
@@ -17,8 +17,29 @@ if (!isset($_SESSION["user"])) {
|
||||
<input type="text" id="title" name="title"
|
||||
value="<?php echo htmlspecialchars($_SESSION['old_title'] ?? ''); unset($_SESSION['old_title']); ?>"
|
||||
placeholder="Titel hier eingeben" required>
|
||||
<textarea id="content" name="content" placeholder="Schreibe deinen Beitrag..."><?php if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])){echo htmlspecialchars($_SESSION['old_content']); unset($_SESSION['old_content']);}elseif (isset($content) && !empty($content)){echo htmlspecialchars($content);}?></textarea>
|
||||
|
||||
<!-- Hier werden die dynamischen divs via JavaScript eingefügt -->
|
||||
<div id="block-container"></div>
|
||||
|
||||
<!-- Plus-Button und das Pop-up-Menü -->
|
||||
<div id="add-block-control" class="add-block-control">
|
||||
<button type="button" id="plus-button" class="plus-button">+</button>
|
||||
<div id="block-popup" class="block-popup hidden">
|
||||
<button type="button" data-type="text">Textblock</button>
|
||||
<button type="button" data-type="image">Bild einfügen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Unsichtbares Textfeld, das die JSON-Daten hält und an den Controller postet -->
|
||||
<textarea id="content" name="content" style="display:none;">
|
||||
<?php
|
||||
if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])){
|
||||
echo htmlspecialchars($_SESSION['old_content']);
|
||||
unset($_SESSION['old_content']);
|
||||
} else {
|
||||
echo '[]'; // Standardmäßig ein leeres JSON-Array
|
||||
}?>
|
||||
</textarea>
|
||||
</main>
|
||||
|
||||
<!-- Seitenleiste -->
|
||||
|
||||
@@ -63,6 +63,11 @@
|
||||
Dein Beitrag wurde erfolgreich veröffentlicht!
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "image_upload_error"): ?>
|
||||
<p class="alert-message is-error">
|
||||
Das Bild konnte nicht hochgeladen werden. Bitte versuche es erneut oder verwende ein anderes Bildformat.
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
unset($_SESSION["message"]);
|
||||
?>
|
||||
|
||||
@@ -42,6 +42,7 @@ if ($pfad === "deleteAccount") {
|
||||
<meta name="author" content="Niklas Ortmann">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/x-icon" href="images/logos/logo_icon.ico">
|
||||
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<link rel="stylesheet" href="css/navbar.css">
|
||||
<link rel="stylesheet" href="css/footer.css">
|
||||
@@ -50,6 +51,8 @@ if ($pfad === "deleteAccount") {
|
||||
<link rel="stylesheet" href="css/profile.css">
|
||||
<link rel="stylesheet" href="css/showArticle.css">
|
||||
<link rel="stylesheet" href="css/message.css">
|
||||
|
||||
<script src="js/editor.js" async></script>
|
||||
<title>EduForge</title>
|
||||
</head>
|
||||
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Editor-Steuerung für dynamische Inhaltsblöcke
|
||||
*/
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const form = document.getElementById("editor-form");
|
||||
|
||||
// Falls das Formular auf der aktuellen Seite nicht existiert, Skript abbrechen
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = document.getElementById("block-container");
|
||||
const plusButton = document.getElementById("plus-button");
|
||||
const popup = document.getElementById("block-popup");
|
||||
const hiddenContentInput = document.getElementById("content");
|
||||
|
||||
// Pop-up umschalten bei Klick auf das Plus
|
||||
plusButton.addEventListener("click", () => {
|
||||
popup.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
// Klick auf eine Block-Option im Pop-up
|
||||
popup.querySelectorAll("button").forEach(btn => {
|
||||
btn.addEventListener("click", function() {
|
||||
const type = this.getAttribute("data-type");
|
||||
addBlockElement(type, "");
|
||||
popup.classList.add("hidden");
|
||||
});
|
||||
});
|
||||
|
||||
// Erstellt ein visuelles HTML-Element im Editor
|
||||
function addBlockElement(type, value = "") {
|
||||
const blockDiv = document.createElement("div");
|
||||
blockDiv.classList.add("editor-block");
|
||||
blockDiv.setAttribute("data-type", type);
|
||||
|
||||
// Löschen-Button für den Block
|
||||
const deleteBtn = document.createElement("button");
|
||||
deleteBtn.type = "button";
|
||||
deleteBtn.innerHTML = "✕";
|
||||
deleteBtn.classList.add("delete-block-btn");
|
||||
deleteBtn.addEventListener("click", () => {
|
||||
blockDiv.remove();
|
||||
});
|
||||
blockDiv.appendChild(deleteBtn);
|
||||
|
||||
if (type === "text") {
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.placeholder = "Schreibe deinen Textblock...";
|
||||
textarea.value = value;
|
||||
blockDiv.appendChild(textarea);
|
||||
} else if (type === "image") {
|
||||
const fileInput = document.createElement("input");
|
||||
fileInput.type = "file";
|
||||
fileInput.accept = "image/*";
|
||||
|
||||
const imgPreview = document.createElement("img");
|
||||
imgPreview.style.maxWidth = "200px";
|
||||
imgPreview.style.display = "block";
|
||||
imgPreview.style.marginTop = "10px";
|
||||
|
||||
// Falls bereits ein Bildpfad existiert (z.B. nach einem Reload bei Validierungsfehlern)
|
||||
if (value && typeof value === 'string' && value.startsWith('uploads/')) {
|
||||
imgPreview.src = value;
|
||||
blockDiv.setAttribute("data-value", value);
|
||||
}
|
||||
|
||||
fileInput.addEventListener("change", function() {
|
||||
if (this.files && this.files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
imgPreview.src = e.target.result;
|
||||
// Temporäre Speicherung des Base64-Strings im HTML-Attribut
|
||||
blockDiv.setAttribute("data-value", e.target.result);
|
||||
}
|
||||
reader.readAsDataURL(this.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
blockDiv.appendChild(fileInput);
|
||||
blockDiv.appendChild(imgPreview);
|
||||
}
|
||||
|
||||
container.appendChild(blockDiv);
|
||||
}
|
||||
|
||||
// Beim Abschicken: HTML-Blöcke auslesen und als JSON-String serialisieren
|
||||
form.addEventListener("submit", function(e) {
|
||||
const blocks = [];
|
||||
container.querySelectorAll(".editor-block").forEach(blockDiv => {
|
||||
const type = blockDiv.getAttribute("data-type");
|
||||
let value = "";
|
||||
|
||||
if (type === "text") {
|
||||
value = blockDiv.querySelector("textarea").value;
|
||||
} else if (type === "image") {
|
||||
value = blockDiv.getAttribute("data-value") || "";
|
||||
}
|
||||
|
||||
blocks.push({ type: type, value: value });
|
||||
});
|
||||
|
||||
// JSON-Daten in das unsichtbare Formularfeld schreiben
|
||||
hiddenContentInput.value = JSON.stringify(blocks);
|
||||
});
|
||||
|
||||
// Existierende Blöcke laden (stellt alte Daten aus der Session wieder her)
|
||||
try {
|
||||
const initialBlocks = JSON.parse(hiddenContentInput.value);
|
||||
if (Array.isArray(initialBlocks)) {
|
||||
initialBlocks.forEach(b => addBlockElement(b.type, b.value));
|
||||
}
|
||||
} catch(e) {
|
||||
// Fallback für alten Reintext aus Altbeständen
|
||||
if (hiddenContentInput.value.trim() !== "") {
|
||||
addBlockElement("text", hiddenContentInput.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -64,10 +64,54 @@ require_once '../validator/article-validator.php';
|
||||
$cleanedTags = array_unique($cleanedTags);
|
||||
$cleanedTags = implode(',', $cleanedTags);
|
||||
}
|
||||
|
||||
// ----------------- Base64-Bilder verarbeiten und auf Server speichern -----------------
|
||||
$blocks = json_decode($content, true);
|
||||
$uploadDir = __DIR__ . '/../../uploads/';
|
||||
|
||||
if (!file_exists($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
|
||||
foreach ($blocks as &$block) {
|
||||
// Base64-Format-Prüfung:
|
||||
if ($block['type'] === 'image' && str_starts_with($block['value'], 'data:image/')) {
|
||||
|
||||
// Base64-String zerlegen:
|
||||
log_alert("Bild erkannt, verarbeite...");
|
||||
$parts = explode(',', $block['value']);
|
||||
$metadata = $parts[0];
|
||||
$base64Data = $parts[1];
|
||||
|
||||
// Dateiendung ermitteln
|
||||
preg_match('/data:image\/(?<extension>.*?);/', $metadata, $matches);
|
||||
$extension = $matches['extension'] ?? 'jpg';
|
||||
if ($extension === 'jpeg') { $extension = 'jpg'; }
|
||||
|
||||
// Eindeutigen Dateinamen generieren
|
||||
$fileName = 'img_' . uniqid() . '.' . $extension;
|
||||
$filePath = $uploadDir . $fileName;
|
||||
|
||||
// Datei im /uploads speichern:
|
||||
if (file_put_contents($filePath, base64_decode($base64Data)) !== false) {
|
||||
// temporären Base64-String durch den echten Pfad ersetzen
|
||||
$block['value'] = 'uploads/' . $fileName;
|
||||
} else {
|
||||
$_SESSION["message"] = "image_upload_error";
|
||||
header("location: ../../index.php?pfad=createArticle");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($block);
|
||||
|
||||
// Aktualisiertes Array wieder in JSON konvertieren
|
||||
$finalContent = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
|
||||
try {
|
||||
$articleManager = ArticleManager::getInstance();
|
||||
$articleManager->addArticle($title, $content, $author, $category, $cleanedTags);
|
||||
$articleManager->addArticle($title, $finalContent, $author, $category, $cleanedTags);
|
||||
|
||||
// Formulardaten nach erfolgreichem Erstellen aus der Session löschen
|
||||
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
|
||||
@@ -84,7 +128,4 @@ require_once '../validator/article-validator.php';
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
||||
@@ -19,7 +19,7 @@ class ArticleManager
|
||||
public static function getInstance()
|
||||
{
|
||||
$articleManager = DatabaseArticleManager::getInstance(); // Hier kann zwischen dem lokalen und datenbankbasiertem ArticleManager gewechselt werden.
|
||||
|
||||
/*
|
||||
// 100 fiktionale Fachbeiträge:
|
||||
$dummyArticles = [
|
||||
// --- INFORMATIK & MATHE (1-20) ---
|
||||
@@ -154,7 +154,7 @@ class ArticleManager
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
return $articleManager;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,19 +33,18 @@ function articleTitleValidator($title)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob der Contenttext 10-7000 Zeichen enthält.
|
||||
* @param $content
|
||||
* Prüft, ob der übergebene Content ein formal valider JSON-String ist.
|
||||
* @param mixed $content Der zu prüfende Inhalt
|
||||
* @return bool
|
||||
*/
|
||||
function articleContentValidator($content)
|
||||
{
|
||||
$content = trim($content);
|
||||
$zeichenAnzahl = mb_strlen($content);
|
||||
if ($zeichenAnzahl <= 7000 && $zeichenAnzahl >= 10) {
|
||||
return true;
|
||||
}else{
|
||||
if (!is_string($content)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prüft direkt, ob der String valides JSON enthält
|
||||
return json_validate($content);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user