finished front and backend
This commit is contained in:
36
addRecipe.js
36
addRecipe.js
@@ -1,36 +0,0 @@
|
||||
function saveRecipe() {
|
||||
const title = document.getElementById("title").value;
|
||||
const recipe = document.getElementById("recipe").value;
|
||||
|
||||
if (title && recipe) {
|
||||
const data = {
|
||||
title: title,
|
||||
recipe: recipe,
|
||||
};
|
||||
|
||||
const jsonString = JSON.stringify(data, null, 2);
|
||||
const blob = new Blob([jsonString], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const errorMsg = document.getElementById("errMsg");
|
||||
if (errorMsg) errorMsg.remove();
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = title;
|
||||
a.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
} else if (title === "" || recipe === "") {
|
||||
throwError("You have to fill in the title and recipe!");
|
||||
}
|
||||
}
|
||||
|
||||
function throwError(message) {
|
||||
const element = document.createElement("p");
|
||||
element.innerText = message;
|
||||
element.id = "errMsg";
|
||||
document.body.appendChild(element);
|
||||
|
||||
console.error(message);
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"title": "Pizza",
|
||||
"recipe": "## Zutaten\n\n### Teig für 3 Pizzen\n\n- **500g** Mehl (Type 405)\n- **250ml** Wasser\n- **21g** Frischhefe (halber Würfel)\n- **10g** Salz\n- **25ml** Olivenöl\n\n### Belag\n\n- Was schmeckt\n\n## Zubereitung\n\n### Teig\n\nAlle Zutaten bis auf das Mehl in eine Schüssel füllen und leicht umrühren.\nDann das Mehl dazuschütten und ca. 5 Minuten mit einem Handrührgerät (Knethaken)\nverkneten. Anschließend noch einmal ca. 10 Minuten per Hand kneten.\n\nDen Teig in einer mit einem feuchten Tuch abgedeckten Schüssel für mindestens 30\nMinuten gehen lassen. Anschließend in 3 gleich große Stücke teilen, noch einmal\nkurz kneten und zu einer Kugel formen. Diese dann in drei kleine Schüssel füllen\nund noch einmal mindestens 60 Minuten gehen lassen.\n\nAbschließend aus den Kugeln mit der Hand runde Pizzaböden formen (nicht mit\nNudelholz!) und nach Belieben belegen.\n\n### Belag\n\nDie Pizza kann belegt werden wie man will. Es lohnt sich, hochwertige Zutaten\nzu verwenden. Außerdem gilt: Weniger ist mehr. Nicht zu dick belegen!\n\n### Backen\n\nHier gilt: Je heißer, desto besser. Im vorgeheizten Ofen bei 300°C Umluft und\nauf einem Pizzastein braucht die Pizza ca. 6-8 Minuten.\nWeder der Pizzastein, noch die 300°C sind unbedingt nötig, aber für das optimale\nErgebnis sollte der Ofen so heiß wie nur irgendwie möglich sein.\n\n### Tipps\n\n- Für einen besonders guten Teig, kann der Teig bereits am Vortag zubereitet\n werden und über Nacht im Kühlschrank gehen. In diesem Fall sollte die\n Hefe-Menge halbiert werden\n- Es ist enorm wichtig, den Teig lange genug zu kneten. Hier darf man auf keinen\n Fall faul sein\n- Den Teig per Hand zu formen erfordert etwas Übung, ist aber relativ schnell\n gelernt. Es lohnt sich, da man mit dem Nudelholz sonst die ganze Luft aus dem\n Teig drückt.\n- Der Pizzateig lässt sich leicht einfrieren. Einfach bereits in runde Form\n bringen, mit Tomatensauce bestreichen und dann einfrieren. Sobald die Pizza\n gefroren ist, in einen Gefrierbeutel packen damit kein Gefrierbrand entsteht."
|
||||
}
|
57
backend/database/recipes/Pizza.txt
Normal file
57
backend/database/recipes/Pizza.txt
Normal file
@@ -0,0 +1,57 @@
|
||||
# Pizza
|
||||
|
||||
## Zutaten
|
||||
|
||||
### Teig für 3 Pizzen
|
||||
|
||||
- **500g** Mehl (Type 405)
|
||||
- **250ml** Wasser
|
||||
- **21g** Frischhefe (halber Würfel)
|
||||
- **10g** Salz
|
||||
- **25ml** Olivenöl
|
||||
|
||||
### Belag
|
||||
|
||||
- Was schmeckt
|
||||
|
||||
## Zubereitung
|
||||
|
||||
### Teig
|
||||
|
||||
Alle Zutaten bis auf das Mehl in eine Schüssel füllen und leicht umrühren.
|
||||
Dann das Mehl dazuschütten und ca. 5 Minuten mit einem Handrührgerät (Knethaken)
|
||||
verkneten. Anschließend noch einmal ca. 10 Minuten per Hand kneten.
|
||||
|
||||
Den Teig in einer mit einem feuchten Tuch abgedeckten Schüssel für mindestens 30
|
||||
Minuten gehen lassen. Anschließend in 3 gleich große Stücke teilen, noch einmal
|
||||
kurz kneten und zu einer Kugel formen. Diese dann in drei kleine Schüssel füllen
|
||||
und noch einmal mindestens 60 Minuten gehen lassen.
|
||||
|
||||
Abschließend aus den Kugeln mit der Hand runde Pizzaböden formen (nicht mit
|
||||
Nudelholz!) und nach Belieben belegen.
|
||||
|
||||
### Belag
|
||||
|
||||
Die Pizza kann belegt werden wie man will. Es lohnt sich, hochwertige Zutaten
|
||||
zu verwenden. Außerdem gilt: Weniger ist mehr. Nicht zu dick belegen!
|
||||
|
||||
### Backen
|
||||
|
||||
Hier gilt: Je heißer, desto besser. Im vorgeheizten Ofen bei 300°C Umluft und
|
||||
auf einem Pizzastein braucht die Pizza ca. 6-8 Minuten.
|
||||
Weder der Pizzastein, noch die 300°C sind unbedingt nötig, aber für das optimale
|
||||
Ergebnis sollte der Ofen so heiß wie nur irgendwie möglich sein.
|
||||
|
||||
### Tipps
|
||||
|
||||
- Für einen besonders guten Teig, kann der Teig bereits am Vortag zubereitet
|
||||
werden und über Nacht im Kühlschrank gehen. In diesem Fall sollte die
|
||||
Hefe-Menge halbiert werden
|
||||
- Es ist enorm wichtig, den Teig lange genug zu kneten. Hier darf man auf keinen
|
||||
Fall faul sein
|
||||
- Den Teig per Hand zu formen erfordert etwas Übung, ist aber relativ schnell
|
||||
gelernt. Es lohnt sich, da man mit dem Nudelholz sonst die ganze Luft aus dem
|
||||
Teig drückt.
|
||||
- Der Pizzateig lässt sich leicht einfrieren. Einfach bereits in runde Form
|
||||
bringen, mit Tomatensauce bestreichen und dann einfrieren. Sobald die Pizza
|
||||
gefroren ist, in einen Gefrierbeutel packen damit kein Gefrierbrand entsteht.
|
@@ -1,16 +1,68 @@
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.render("index");
|
||||
const recipesFolder = path.join(__dirname, "database/recipes");
|
||||
fs.readdir(recipesFolder, (err, files) => {
|
||||
if (err) {
|
||||
return res.status(500).send("Error by reading recipe files!");
|
||||
}
|
||||
|
||||
const txtFiles = files.filter((file) => file.endsWith(".txt"));
|
||||
|
||||
const recipes = txtFiles.map((file) => {
|
||||
const content = fs.readFileSync(path.join(recipesFolder, file), "utf8");
|
||||
return {
|
||||
title: path.basename(file, ".txt"),
|
||||
content: content,
|
||||
};
|
||||
});
|
||||
|
||||
res.render("index", { recipes });
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/add", (req, res) => {
|
||||
res.render("addRecipe/index");
|
||||
});
|
||||
|
||||
// middleware for add recipe
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.json());
|
||||
|
||||
// add recipe script
|
||||
app.post("/add/create-recipe", (req, res) => {
|
||||
const { filename, content } = req.body;
|
||||
|
||||
const directory = path.join(__dirname, "database/recipes");
|
||||
// left out if statement for directory check
|
||||
/*
|
||||
if (!fs.existsSync(directory)) {
|
||||
fs.mkdirSync(directory);
|
||||
}
|
||||
*/
|
||||
|
||||
const filePath = path.join(directory, `${filename}.txt`);
|
||||
|
||||
fs.writeFile(filePath, content, (err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res
|
||||
.status(500)
|
||||
.console.error("Error by creating file")
|
||||
.send("Error by creating file. Please check browser console!");
|
||||
}
|
||||
res.render("success", {
|
||||
filename: filename,
|
||||
path: filePath,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const recipeRouter = require("./routes/recipes");
|
||||
|
||||
app.use("/recipe", recipeRouter);
|
||||
|
@@ -3,140 +3,154 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Add Recipe</title>
|
||||
<script src="addRecipe.js" defer></script>
|
||||
|
||||
<style>
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "San Francisco",
|
||||
"Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
<title>Add recipe</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<h1>Add a New Recipe</h1>
|
||||
<h1>Add a new recipe</h1>
|
||||
<form action="/add/create-recipe" method="POST">
|
||||
<label for="title">Recipe Title</label>
|
||||
<input
|
||||
type="text"
|
||||
name="filename"
|
||||
required
|
||||
id="title"
|
||||
placeholder="e.g. Grandma's Apple Pie"
|
||||
/>
|
||||
|
||||
<label for="title">Recipe Title</label>
|
||||
<input type="text" id="title" placeholder="e.g. Grandma's Apple Pie" />
|
||||
<label for="recipe">Recipe instructions</label>
|
||||
<textarea
|
||||
required
|
||||
name="content"
|
||||
id="recipe"
|
||||
placeholder="Write your recipe here… You can use markdown!"
|
||||
></textarea>
|
||||
<small>You can use Markdown syntax for formatting.</small>
|
||||
|
||||
<label for="recipe">Recipe Instructions</label>
|
||||
<textarea
|
||||
id="recipe"
|
||||
placeholder="Write your recipe here… You can use markdown!"
|
||||
></textarea>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="saveBtn" onclick="saveRecipe()">Save Recipe</button>
|
||||
<a href="/" class="back-btn">Back to Recipes</a>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button type="submit">Save recipe</button>
|
||||
<a href="/" class="back-btn">Back to recipes</a>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
/* Reset basic styles */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "San Francisco",
|
||||
"Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f5f5f7;
|
||||
color: #1d1d1f;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #ffffff;
|
||||
padding: 2rem;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
body {
|
||||
background-color: #f5f5f7;
|
||||
color: #1d1d1f;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1.5rem;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
.container {
|
||||
background-color: #ffffff;
|
||||
padding: 2.5rem;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1.5rem;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid #d1d1d6;
|
||||
border-radius: 12px;
|
||||
background-color: #f9f9fa;
|
||||
font-size: 1rem;
|
||||
transition: border 0.2s ease;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 1.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
textarea:focus {
|
||||
border-color: #007aff;
|
||||
outline: none;
|
||||
background-color: #fff;
|
||||
}
|
||||
input[type="text"],
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid #d1d1d6;
|
||||
border-radius: 12px;
|
||||
background-color: #f9f9fa;
|
||||
font-size: 1rem;
|
||||
transition: border 0.2s ease;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 150px;
|
||||
}
|
||||
input[type="text"]:focus,
|
||||
textarea:focus {
|
||||
border-color: #007aff;
|
||||
outline: none;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
button,
|
||||
.back-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
small {
|
||||
display: block;
|
||||
margin-top: 0.4rem;
|
||||
color: #6e6e73;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
button#saveBtn {
|
||||
background-color: #007aff;
|
||||
color: white;
|
||||
}
|
||||
.button-group {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
button#saveBtn:hover {
|
||||
background-color: #005ecb;
|
||||
}
|
||||
button,
|
||||
.back-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background-color: #e5e5ea;
|
||||
color: #1d1d1f;
|
||||
}
|
||||
button[type="submit"] {
|
||||
background-color: #007aff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: #d1d1d6;
|
||||
}
|
||||
</style>
|
||||
button[type="submit"]:hover {
|
||||
background-color: #005ecb;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background-color: #e5e5ea;
|
||||
color: #1d1d1f;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: #d1d1d6;
|
||||
}
|
||||
</style>
|
@@ -4,7 +4,6 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Cookbook</title>
|
||||
<script src="index.js" defer></script>
|
||||
|
||||
<!-- Apple-Style: San Francisco Font und Styling -->
|
||||
</head>
|
||||
@@ -13,10 +12,8 @@
|
||||
<header>
|
||||
<h1>Cookbook</h1>
|
||||
<div class="button-group">
|
||||
<button onclick="syncRecipes()">🔄 Sync Recipes</button>
|
||||
<a href="/add" class="secondary-button"
|
||||
>➕ Add Recipe</a
|
||||
>
|
||||
<a href="/"><button>🔄 Sync Recipes</button></a>
|
||||
<a href="/add" class="secondary-button">➕ Add Recipe</a>
|
||||
<a
|
||||
href="https://git.the1s.de/theis.gaedigk/Cookbook/src/branch/main/helpSite.md"
|
||||
class="secondary-button"
|
||||
@@ -28,7 +25,12 @@
|
||||
</header>
|
||||
|
||||
<section id="recipes" class="recipes-list">
|
||||
<!-- Recipes will be dynamically loaded here -->
|
||||
<% recipes.forEach(recipe => { %>
|
||||
<div class="recipe-card">
|
||||
<h2><%= recipe.title %></h2>
|
||||
<pre><%= recipe.content %></pre>
|
||||
</div>
|
||||
<% }) %>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
|
114
backend/views/success.ejs
Normal file
114
backend/views/success.ejs
Normal file
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Success!</title>
|
||||
|
||||
<style>
|
||||
/* San Francisco / Apple System Font */
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "San Francisco",
|
||||
"Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f5f5f7;
|
||||
color: #1d1d1f;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
padding: 2.5rem;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button,
|
||||
.btn-link {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007aff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #005ecb;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #e5e5ea;
|
||||
color: #1d1d1f;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #d1d1d6;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>✅ Recipe created successfully!</h1>
|
||||
<p>Filename: <strong><%= filename %></strong></p>
|
||||
<p>File path: <strong><%= path %></strong></p>
|
||||
|
||||
<div class="buttons">
|
||||
<a href="/add"
|
||||
><button class="btn-secondary">➕ Add more recipes</button></a
|
||||
>
|
||||
<a href="/"><button>📖 View all recipes</button></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user