Merge branch 'feature/inhaltseditor-frontend' into 'dev'

Optimierungen Frontend Inhaltseditor

See merge request eb2342s/swe-b1-a!32
This commit was merged in pull request #92.
This commit is contained in:
Eric Blommel
2025-01-07 00:06:57 +01:00
8 changed files with 322 additions and 76 deletions

View File

@@ -0,0 +1,158 @@
function createFormula(text = "") {
const formulaInputs = document.querySelector('#formulaInputs');
const formulaElement = document.createElement('div');
formulaElement.classList.add('input-formula', "pb-10");
formulaElement.id = "f-" + crypto.randomUUID();
const textElement = document.createElement('textarea');
textElement.type = 'text';
textElement.classList.add('input-text');
textElement.classList.add("w-full", "px-4", "py-3", "border", "rounded-lg", "focus:outline-none", "focus:ring-2", "focus:ring-blue-500", "text-lg", "enabled:hover:border-gray-400");
textElement.rows = 3;
textElement.placeholder = "Aufgabentext";
textElement.innerText = text;
textElement.onchange = updateFormulaText;
formulaElement.appendChild(textElement);
const variableSection = document.createElement('div');
variableSection.classList.add("input-variable-section");
formulaElement.appendChild(variableSection)
const buttonRow = document.createElement('div');
buttonRow.classList.add("flex", "flow-root");
const removeFormulaButton = document.createElement('button');
removeFormulaButton.type = "button";
removeFormulaButton.innerText = "Aufgabe löschen";
removeFormulaButton.classList.add("px-4", "py-3", "border", "rounded-lg", "focus:outline-none", "focus:ring-2", "focus:ring-blue-500", "text-lg", "enabled:hover:border-gray-400");
removeFormulaButton.onclick = () => {
formulaElement.remove();
};
buttonRow.appendChild(removeFormulaButton);
const addVariableButton = document.createElement('button');
addVariableButton.type = "button";
addVariableButton.innerText = "+";
addVariableButton.classList.add("w-2/12", "md:w-1/12", "float-right", "px-4", "py-3", "border", "rounded-lg", "focus:outline-none", "focus:ring-2", "focus:ring-blue-500", "text-lg", "enabled:hover:border-gray-400");
addVariableButton.onclick = () => {
createVariable(formulaElement.id);
};
buttonRow.appendChild(addVariableButton);
formulaElement.appendChild(buttonRow);
formulaInputs.appendChild(formulaElement);
return formulaElement.id;
}
function createVariable(formulaUID, variableText = "", valueText = "") {
const element = document.querySelector("#" + formulaUID).querySelector(".input-variable-section");
const variableValueElement = document.createElement('div');
variableValueElement.classList.add('input-variables');
const variableElement = document.createElement('input');
variableElement.type = 'text';
variableElement.classList.add('input-variable');
variableElement.classList.add("w-5/12", "md:w-6/12", "px-4", "py-3", "border", "rounded-lg", "focus:outline-none", "focus:ring-2", "focus:ring-blue-500", "text-lg", "enabled:hover:border-gray-400")
variableElement.value = variableText;
variableElement.placeholder = "Variable";
variableElement.onchange = updateFormulaText;
variableValueElement.appendChild(variableElement);
const valueElement = document.createElement('input');
valueElement.type = 'text';
valueElement.classList.add('input-value');
valueElement.classList.add("w-5/12", "md:w-5/12", "px-4", "py-3", "border", "rounded-lg", "focus:outline-none", "focus:ring-2", "focus:ring-blue-500", "text-lg", "enabled:hover:border-gray-400")
valueElement.value = valueText;
valueElement.placeholder = "Richtiger Wert";
valueElement.onchange = updateFormulaText;
variableValueElement.appendChild(valueElement);
const removeButton = document.createElement('button');
removeButton.type = "button";
removeButton.innerText = "-"
removeButton.classList.add("w-2/12", "md:w-1/12", "px-4", "py-3", "border", "rounded-lg", "focus:outline-none", "focus:ring-2", "focus:ring-blue-500", "text-lg", "enabled:hover:border-gray-400");
removeButton.onclick = () => {
variableValueElement.remove();
updateFormulaText();
};
variableValueElement.appendChild(removeButton);
element.appendChild(variableValueElement);
}
function createFormulaWithVariable() {
const uuid = createFormula();
createVariable(uuid);
}
function createFormulasFromString(string) {
const taskStrings = string.split(";;;;");
taskStrings.forEach((taskString) => {
const elements = taskString.split(";;");
if(elements.length < 2) {
return;
}
const text = elements[0];
const variables = elements.slice(1, elements.length);
const uid = createFormula(text);
variables.forEach((variableValuePair) => {
const arr = variableValuePair.split("::");
if(arr.length !== 2) {
return;
}
createVariable(uid, arr[0], arr[1]);
});
});
}
function getFormulasAsString() {
const formulaInputs = document.querySelector('#formulaInputs');
let formulas = [];
formulaInputs.querySelectorAll('.input-formula').forEach((formulaElement) => {
const text = formulaElement.querySelector(".input-text").value;
if(text.includes(";;")) {
return;
}
let variables = [];
formulaElement.querySelectorAll(".input-variables").forEach((variableValueElement) => {
const variable = variableValueElement.querySelector(".input-variable");
const value = variableValueElement.querySelector(".input-value");
if(variable == null || value == null) {
return;
}
if(variable.value.trim() === "" || value.value.trim() === "") {
return;
}
if(variable.value.includes(";;") || variable.value.includes("::")) {
return;
}
if(value.value.includes(";;") || value.value.includes("::")) {
return;
}
variables.push(variable.value + "::" + value.value);
});
formulas.push(text + ";;" + variables.join(";;"));
});
return formulas.join(';;;;');
}
function updateFormulaText() {
document.querySelector('#formulas').value = getFormulasAsString();
}

View File

@@ -1,62 +1,56 @@
[
{
"text": "Ein Quadrat mit Seitenlänge $$a = 4 cm$$ berechne die Fläche",
"vars": {
"Fläche in cm^2": "16"
{
"text": "Ein Quadrat mit Seitenlänge \\(a = 4 cm\\) berechne die Fläche",
"vars": {
"Fläche in cm^2": "16"
}
},
{
"text": "Ein Rechteck mit \\(l = 7 cm\\) und \\(b = 2 cm\\) berechne die Fläche",
"vars": {
"Fläche in cm^2": "14"
}
},
{
"text": "Berechne den Umfang \\(U\\) eines Quadrats mit \\(a = 6 cm\\)",
"vars": {
"U in cm": "24"
}
},
{
"text": "Berechne den Umfang \\(U\\) eines Rechteck mit \\(l = 8 cm\\) und \\(b = 4 cm\\)",
"vars": {
"?": "24"
}
},
{
"text": "Ein Dreieck mit der Grundlinie \\(G = 8cm\\) und einer Höhe \\(h= 5cm\\) berechne die Fläche",
"vars": {
"Fläche in cm^2": "20"
}
},
{
"text": "Ein Dreieck mit der Grundlinie \\(G = 10cm\\) und einer Höhe \\(h= 7cm\\) berechne die Fläche",
"vars": {
"Fläche in cm^2": "35"
}
},
{
"text": "Ein Kreis hat den Radius \\(r = 5cm\\) Berechne den Umfang. Runde auf eine NkSt",
"vars": {
"Umfang in cm": "31,4"
}
},
{
"text": "Ein Quader hat den die Länge \\(l = 4cm\\) Breite \\(b = 3cm\\) und Höhe \\(h=2cm\\) Berechne das Volumen \\(V\\)",
"vars": {
"Volumen in cm^3": "24"
}
},
{
"text": "Ein Quader hat den die Länge \\(l = 6cm\\) Breite \\(b = 4cm\\) und Höhe \\(h=3cm\\) Berechne das Volumen \\(V\\)",
"vars": {
"Volumen in cm^3": "72"
}
}
},
{
"text": "Ein Rechteck mit $$l = 7 cm$$ und $$b = 2 cm$$ berechne die Fläche",
"vars": {
"Fläche in cm^2": "14"
}
},
{
"text": "Berechne den Umfang $$U$$ eines Quadrats mit $$a = 6 cm$$",
"vars": {
"Umfang in cm": "24"
}
},
{
"text": "Berechne den Umfang $$U$$ eines Rechteck mit $$l = 8 cm$$ und $$b = 4 cm$$",
"vars": {
"U in cm": "5/15"
}
},
{
"text": "Ein Dreieck mit der Grundlinie $$G = 8cm$$ und einer Höhe $$h= 5cm$$ berechne die Fläche",
"vars": {
"Fläche in cm^2": "20"
}
},
{
"text": "Ein Dreieck mit der Grundlinie $$G = 10cm$$ und einer Höhe $$h= 7cm$$ berechne die Fläche",
"vars": {
"Fläche in cm^2": "35"
}
},
{
"text": "Ein Kreis hat den Radius $$r = 4cm$$ Berechne den Umfang. Runde auf eine NkSt",
"vars": {
"Umfang in cm": "25,1"
}
},
{
"text": "Ein Kreis hat den Radius $$r = 5cm$$ Berechne den Umfang. Runde auf eine NkSt",
"vars": {
"Umfang in cm": "31,4"
}
},
{
"text": "Ein Quader hat den die Länge $$l = 4cm$$ Breite $$b = 3cm$$ und Höhe $$h=2cm$$ Berechne das Volumen $$V$$",
"vars": {
"Volumen in cm^3": "24"
}
},
{
"text": "Ein Quader hat den die Länge $$l = 6cm$$ Breite $$b = 4cm$$ und Höhe $$h=3cm$$ Berechne das Volumen $$V$$",
"vars": {
"Volumen in cm^3": "72"
}
}
]

View File

@@ -1,14 +1,14 @@
[
{
"text": "Was ist die Wahrscheinlichkeit $$W$$, bei einem Würfel eine gerade Zahl zu würfeln?",
"text": "Was ist die Wahrscheinlichkeit \\(W\\), bei einem Würfel eine gerade Zahl zu würfeln?",
"vars": {
"W": "1/2"
"W": "1\/2"
}
},
{
"text": "Was ist die Wahrscheinlichkeit $$W$$ für eine rote Karte aus einem Kartenspiel (52 Karten, 26 rot)",
"text": "Was ist die Wahrscheinlichkeit \\(W\\) für eine rote Karte aus einem Kartenspiel (52 Karten, 26 rot)",
"vars": {
"W": "26/52"
"W": "26\/52"
}
}
]

View File

@@ -69,7 +69,7 @@ session_start();
<div class="w-12 h-12 rounded-lg bg-gray-100 flex items-center justify-center">
<i class="fas <?php echo($subject->icon); ?> text-2xl text-[<?php echo($subject->color); ?>]"></i>
</div>
<div class="flex-1">
<div class="flex-1 overflow-hidden">
<h3 class="text-xl font-bold text-gray-900 mb-2"><?php echo($subject->displayName); ?></h3>
<p class="text-gray-600 mb-4"><?php echo($subject->description); ?></p>
<div class="grid grid-cols-3 gap-4 mb-4">

View File

@@ -47,13 +47,13 @@ $topics = $subjectData->topics;
<div class="sidebar-header">
<i class="fas <?php echo($subjectData->icon); ?>"></i>
<h2><?php echo($subjectData->displayName); ?></h2>
<h2 class="overflow-hidden"><?php echo($subjectData->displayName); ?></h2>
</div>
<a href="index.php" class="nav-link">
<i class="fas fa-home"></i> Startseite
</a>
<a href="#" class="nav-link active">
<a href="#" class="nav-link active overflow-hidden">
<i class="fas fa-book"></i> <?php echo($subjectData->displayName); ?> Übersicht
</a>
</nav>
@@ -69,7 +69,7 @@ $topics = $subjectData->topics;
<div class="topic-card"
onclick="window.location.href='topic.php?subject=<?php echo($topicData->subjectId); ?>&topic=<?php echo($topicData->id); ?>'"
style="cursor: pointer;">
<div class="topic-header">
<div class="topic-header overflow-hidden">
<i class="fas <?php echo($topicData->icon); ?> topic-icon text-[<?php echo($subjectData->color); ?>]"></i>
<h3 class="topic-title"><?php echo($topicData->displayName); ?></h3>
</div>
@@ -102,6 +102,10 @@ $topics = $subjectData->topics;
</ul>
</div>-->
</div>
<?php
if(count($topicData->files) > 0) {
?>
<div class="download-section bg-gray-100 p-[20px] rounded-[20px]">
<h4>Übungen herunterladen:</h4>
<div class="download-links">
@@ -122,6 +126,10 @@ $topics = $subjectData->topics;
?>
</div>
</div>
<?php
}
?>
</div>
<?php

View File

@@ -159,7 +159,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Icon</label>
<div class="mt-2 h-[180px] overflow-y-auto grid sm:grid-cols-5 lg:grid-cols-8 grid-cols-4 gap-2 p-1">
<div class="mt-2 h-[180px] overflow-y-auto grid grid-cols-5 sm:grid-cols-8 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10 gap-2 p-1">
<?php
$icons = [
"fa-book", "fa-calculator", "fa-flask", "fa-atom", "fa-globe-europe", "fa-palette",

View File

@@ -33,7 +33,6 @@ if (!isset($topicData)) {
<link href="assets/css/topic.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<!--<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>-->
<script src="assets/js/sidebar.js"></script>
<script src="assets/js/tasks.js" defer></script>
@@ -82,10 +81,10 @@ if (!isset($topicData)) {
<a href="index.php" class="nav-link">
<i class="fas fa-home"></i> Startseite
</a>
<a href="subject.php?subject=<?php echo($subjectData->id); ?>" class="nav-link">
<a href="subject.php?subject=<?php echo($subjectData->id); ?>" class="nav-link overflow-hidden">
<i class="fas fa-book"></i> <?php echo($subjectData->displayName); ?> Übersicht
</a>
<a href="#" class="nav-link active">
<a href="#" class="nav-link active overflow-hidden">
<i class="fas <?php echo($topicData->icon); ?>"></i> <?php echo($topicData->displayName); ?>
</a>
</nav>
@@ -95,6 +94,10 @@ if (!isset($topicData)) {
<div class="max-w-7xl mx-auto mt-5">
<div class="mt-16"></div>
<?php
if(count($topicData->relatedTopics) > 0) {
?>
<!-- Related Topics -->
<div class="related-topics bg-gray-100 p-4 rounded-lg">
<h4>Verwandte Themen:</h4>
@@ -119,6 +122,10 @@ if (!isset($topicData)) {
</ul>
</div>
<?php
}
?>
<!-- Topic Content -->
<div class="content-card mt-[8px]">
<h1 class="content-title"><?php echo($topicData->displayName); ?></h1>
@@ -129,6 +136,9 @@ if (!isset($topicData)) {
<?php echo($topicData->getFinishedArticle()); ?>
</div>
<?php
if(count($topicData->files) > 0) {
?>
<div class="exercise-section bg-gray-100">
<h3 style="margin-bottom: 1rem;" class="text-[var(--primary-color)]">Übungen herunterladen:</h3>
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
@@ -148,6 +158,10 @@ if (!isset($topicData)) {
?>
</div>
</div>
<?php
}
?>
</div>
<!-- Tasks -->
@@ -206,6 +220,9 @@ if (!isset($topicData)) {
?>
<?php
if(count($topicData->relatedTopics) > 0) {
?>
<!-- Related Topics -->
<div class="related-topics bg-gray-100 p-4 rounded-lg">
<h4>Verwandte Themen:</h4>
@@ -229,6 +246,10 @@ if (!isset($topicData)) {
?>
</ul>
</div>
<?php
}
?>
</div>
</main>

View File

@@ -52,8 +52,8 @@ if (isset($_GET['subject']) && isset($_GET['topic'])) {
$defaultValues['formulas'] = implode(";;;;", $tasks);
$defaultValues['article'] = $editingTopic->getFinishedArticle();
}
} else if (isset($_GET['subject'])) {
$defaultValues['subjectId'] = $_GET['subject'];
} else if (isset($_GET['subjectId'])) {
$defaultValues['subjectId'] = $_GET['subjectId'];
}
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
@@ -189,6 +189,8 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// extension=mbstring in php.ini muss aktiviert sein!
$article = mb_convert_encoding($dom->saveHTML(), 'UTF-8', 'HTML-ENTITIES');
$article = preg_replace("/^[ \r\n\t]*<p><p>/", "<p>", $article);
$article = preg_replace("#</p></p>[ \r\n\t]*$#", "</p>", $article);
if (isset($allSubjects[$_POST['subjectId']]->getTopics()[$_POST['id']])) {
$newTopic = $allSubjects[$_POST['subjectId']]->getTopics()[$_POST['id']];
@@ -261,6 +263,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
integrity="sha384-RdymN7NRJ+XoyeRY4185zXaxq9QWOOx3O7beyyrRK4KQZrPlCDQQpCu95FoCGPAE"
crossorigin="anonymous"></script>
<script src="assets/js/katex_autorender_mod.js"></script>
<script src="assets/js/formula.js"></script>
</head>
<body class="min-h-screen bg-gray-50">
@@ -322,7 +325,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
<div>
<label class="block text-sm font-medium text-gray-700">Icon</label>
<div class="mt-2 h-[180px] overflow-y-auto grid grid-cols-8 gap-2 p-1">
<div class="mt-2 h-[180px] overflow-y-auto grid grid grid-cols-5 sm:grid-cols-8 md:grid-cols-10 gap-2 p-1">
<?php
$icons = [
"fa-book", "fa-calculator", "fa-flask", "fa-atom", "fa-globe-europe", "fa-palette",
@@ -339,6 +342,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
</div>
<input type="hidden" id="selectedIcon" name="icon" value="<?php echo $defaultValues['icon']; ?>">
</div>
<div>
<label for="relatedTopicSelect" class="block text-sm font-medium text-gray-700 mb-2">Verwandte Themen (IDs, kommagetrennt)</label>
<input type="text"
@@ -346,10 +350,33 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
id="relatedTopicSelect"
class="w-full px-4 py-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-lg enabled:hover:border-gray-400"
value="<?php echo $defaultValues['relatedTopics']; ?>">
<?php
if ($defaultValues['subjectId'] !== "") {
?>
<select id="addRelatedTopic"
onchange="addSelectedRelatedTopic()"
class="w-full px-4 py-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-lg enabled:hover:border-gray-400 appearance-none bg-white">
<option selected value="">Thema hinzufügen</option>
<?php
foreach ($allSubjects[$defaultValues['subjectId']]->getTopics() as $topic) {
if(isset($editingTopic) && $topic->getId() === $editingTopic->getId()) {
continue;
}
echo "<option value='" . $topic->getId() . "'>" . $topic->getDisplayName() . "</option>";
}
?>
</select>
<?php
}
?>
</div>
<div>
<label for="existing_files" class="block text-sm font-medium text-gray-700 mb-2">Existierende Übungsblätter</label>
<label for="existing_files" class="block text-sm font-medium text-gray-700 mb-2">Existierende Übungsblätter - Entfernen zum Löschen</label>
<input type="text"
name="existing_files"
id="existing_files"
@@ -378,7 +405,18 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
id="formulas"
class="w-full px-4 py-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-lg enabled:hover:border-gray-400"
placeholder="Format: aufgabentext1;;a::2,5;;b::3;;;;aufgabentext2;;a::1"
hidden="hidden"
value="<?php echo $defaultValues['formulas']; ?>">
<div id="formulaInputs">
</div>
<button class="mt-0 border-2 border-blue-500 text-blue-500 w-full px-4 py-2 rounded-lg hover:bg-blue-500 hover:text-white transition duration-300 flex items-center"
onclick="createFormulaWithVariable()"
type="button">
<span>Neue Aufgabe</span>
</button>
</div>
</div>
@@ -400,7 +438,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
</div>
</div>
<div id="quillEditor" style="height: 600px;" class="bg-white border rounded-lg"><?php
<div id="quillEditor" class="h-[600px] xl:h-auto bg-white border rounded-lg"><?php
$article = $defaultValues['article'];
echo $article;
@@ -410,6 +448,12 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
</div>
</div>
<?php
foreach($errors as $error) {
echo $error;
}
?>
<div class="flow-root gap-4">
<?php if (isset($editingTopic)) { ?>
<input type="submit" value="Thema löschen" name="submit"
@@ -442,6 +486,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
});
}
createFormulasFromString(document.querySelector('#formulas').value);
renderFormulas();
const quill = new Quill('#quillEditor', {
@@ -509,6 +554,26 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
iconInput.value = element.getAttribute('data-icon');
}
function addSelectedRelatedTopic() {
const input = document.querySelector('#relatedTopicSelect');
const selector = document.querySelector('#addRelatedTopic');
if(selector.value === "") {
return;
}
if(input.value.includes(selector.value)) {
return;
}
if(input.value.trim() === "") {
input.value += selector.value;
} else {
input.value += ", " + selector.value;
}
selector.selectedIndex = 0;
}
</script>
</body>