Edit page: auto-expand step textareas, wider layout, image management
- Textareas auto-resize to fit content (min 120px) - Edit page container widened to 1100px - Show recipe image with URL input and file upload options - Add image upload endpoint (POST /api/recipes/<backend>/<id>/image) - Add upload_image_bytes() to both Mealie and Tandoor clients Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+148
-16
@@ -3,13 +3,10 @@
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
/* Wider layout for edit page */
|
||||
.container { max-width: 1100px; }
|
||||
|
||||
.section-divider { border: none; border-top: 1px solid var(--border); margin: 1.2rem 0; }
|
||||
.recipe-image {
|
||||
max-width: 300px;
|
||||
max-height: 200px;
|
||||
border-radius: var(--radius);
|
||||
object-fit: cover;
|
||||
}
|
||||
.ingredient-header {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
@@ -84,7 +81,7 @@
|
||||
flex-shrink: 0;
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
.instruction-row textarea { margin-bottom: 0; flex: 1; min-height: 120px; }
|
||||
.instruction-row textarea { margin-bottom: 0; flex: 1; min-height: 120px; overflow: hidden; }
|
||||
.instruction-row button {
|
||||
background: var(--danger);
|
||||
border: none;
|
||||
@@ -179,6 +176,56 @@
|
||||
}
|
||||
.edit-top-bar h2 { margin-bottom: 0; flex: 1; }
|
||||
|
||||
/* Image section */
|
||||
.image-section {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.image-preview {
|
||||
max-width: 350px;
|
||||
max-height: 250px;
|
||||
border-radius: var(--radius);
|
||||
object-fit: cover;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.image-controls {
|
||||
margin-top: 0.75rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.image-controls .url-input-group {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
.image-controls .url-input-group input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.image-controls .url-input-group label {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.file-upload-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.6rem 1rem;
|
||||
background: var(--surface2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-dim);
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: border-color 0.15s, color 0.15s;
|
||||
}
|
||||
.file-upload-label:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--text);
|
||||
}
|
||||
.image-upload-status {
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading-wrap {
|
||||
text-align: center;
|
||||
@@ -221,17 +268,31 @@
|
||||
<a href="/recipes" class="btn btn-secondary">← Vissza</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mb-2">
|
||||
<div class="grow">
|
||||
<label for="recipeTitle">Név</label>
|
||||
<input type="text" id="recipeTitle">
|
||||
<label for="recipeTitle">Név</label>
|
||||
<input type="text" id="recipeTitle">
|
||||
|
||||
<label for="recipeDesc">Leírás</label>
|
||||
<textarea id="recipeDesc" rows="2"></textarea>
|
||||
</div>
|
||||
<label for="recipeDesc">Leírás</label>
|
||||
<textarea id="recipeDesc" rows="2"></textarea>
|
||||
|
||||
<!-- Image section -->
|
||||
<div class="image-section">
|
||||
<label>Kép</label>
|
||||
<div>
|
||||
<img id="recipeImage" class="recipe-image" src="" alt="" style="display:none;">
|
||||
<img id="recipeImage" class="image-preview" src="" alt="" style="display:none;">
|
||||
<div id="noImageText" class="text-dim" style="display:none; font-size:0.9rem;">Nincs kép</div>
|
||||
</div>
|
||||
<div class="image-controls">
|
||||
<div class="url-input-group">
|
||||
<label for="imageUrlInput">Kép URL</label>
|
||||
<input type="url" id="imageUrlInput" placeholder="https://...">
|
||||
</div>
|
||||
<button class="btn btn-secondary" onclick="setImageFromUrl()" id="setUrlBtn" style="margin-bottom:0;">Beállítás</button>
|
||||
<label class="file-upload-label" style="margin-bottom:0;">
|
||||
📷 Fájl feltöltése
|
||||
<input type="file" id="imageFileInput" accept="image/*" onchange="uploadImageFile()" style="display:none;">
|
||||
</label>
|
||||
</div>
|
||||
<div id="imageUploadStatus" class="image-upload-status"></div>
|
||||
</div>
|
||||
|
||||
<hr class="section-divider">
|
||||
@@ -333,10 +394,17 @@ function populateForm(r) {
|
||||
document.getElementById('recipeTitle').value = r.title || '';
|
||||
document.getElementById('recipeDesc').value = r.description || '';
|
||||
|
||||
// Image
|
||||
const img = document.getElementById('recipeImage');
|
||||
const noImg = document.getElementById('noImageText');
|
||||
if (r.image_url) {
|
||||
img.src = r.image_url;
|
||||
img.style.display = 'block';
|
||||
noImg.style.display = 'none';
|
||||
document.getElementById('imageUrlInput').value = r.image_url;
|
||||
} else {
|
||||
img.style.display = 'none';
|
||||
noImg.style.display = 'block';
|
||||
}
|
||||
|
||||
// Ingredients
|
||||
@@ -379,6 +447,13 @@ function addIngredientGroup(name) {
|
||||
list.appendChild(row);
|
||||
}
|
||||
|
||||
/* ===== Auto-resize textarea ===== */
|
||||
function autoResize(ta) {
|
||||
ta.style.height = 'auto';
|
||||
const h = Math.max(120, ta.scrollHeight);
|
||||
ta.style.height = h + 'px';
|
||||
}
|
||||
|
||||
/* ===== Instructions ===== */
|
||||
function addInstruction(value) {
|
||||
const list = document.getElementById('instructionsList');
|
||||
@@ -389,6 +464,10 @@ function addInstruction(value) {
|
||||
+ '<textarea>' + escHtml(value) + '</textarea>'
|
||||
+ '<button onclick="removeInstruction(this)">✕</button>';
|
||||
list.appendChild(row);
|
||||
const ta = row.querySelector('textarea');
|
||||
ta.addEventListener('input', function() { autoResize(this); });
|
||||
// Auto-resize after content is set
|
||||
setTimeout(() => autoResize(ta), 0);
|
||||
}
|
||||
|
||||
function removeInstruction(btn) {
|
||||
@@ -499,6 +578,59 @@ document.addEventListener('click', e => {
|
||||
}
|
||||
});
|
||||
|
||||
/* ===== Image ===== */
|
||||
function setImageFromUrl() {
|
||||
const url = document.getElementById('imageUrlInput').value.trim();
|
||||
if (!url) return;
|
||||
const img = document.getElementById('recipeImage');
|
||||
const noImg = document.getElementById('noImageText');
|
||||
img.src = url;
|
||||
img.style.display = 'block';
|
||||
noImg.style.display = 'none';
|
||||
if (currentRecipe) currentRecipe.image_url = url;
|
||||
showToast('Kép URL beállítva. Mentsd a receptet a véglegesítéshez.', 'success');
|
||||
}
|
||||
|
||||
async function uploadImageFile() {
|
||||
const fileInput = document.getElementById('imageFileInput');
|
||||
const file = fileInput.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const status = document.getElementById('imageUploadStatus');
|
||||
status.innerHTML = '<span class="spinner"></span> Feltöltés...';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/recipes/' + BACKEND + '/' + encodeURIComponent(RECIPE_ID) + '/image', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.ok) {
|
||||
status.innerHTML = '<span class="text-success">✓ Kép feltöltve</span>';
|
||||
// Reload recipe to get new image URL
|
||||
const r2 = await fetch('/api/recipes/' + BACKEND + '/' + encodeURIComponent(RECIPE_ID));
|
||||
const d2 = await r2.json();
|
||||
if (d2.ok && d2.recipe.image_url) {
|
||||
const img = document.getElementById('recipeImage');
|
||||
img.src = d2.recipe.image_url + '?t=' + Date.now();
|
||||
img.style.display = 'block';
|
||||
document.getElementById('noImageText').style.display = 'none';
|
||||
document.getElementById('imageUrlInput').value = d2.recipe.image_url;
|
||||
if (currentRecipe) currentRecipe.image_url = d2.recipe.image_url;
|
||||
}
|
||||
showToast('Kép sikeresen feltöltve.', 'success');
|
||||
} else {
|
||||
status.innerHTML = '<span class="text-danger">Hiba: ' + escHtml(data.error) + '</span>';
|
||||
}
|
||||
} catch (e) {
|
||||
status.innerHTML = '<span class="text-danger">Hiba: ' + escHtml(e.message) + '</span>';
|
||||
}
|
||||
fileInput.value = '';
|
||||
}
|
||||
|
||||
/* ===== Gather form data ===== */
|
||||
function gatherRecipe() {
|
||||
const ingredients = [];
|
||||
@@ -531,7 +663,7 @@ function gatherRecipe() {
|
||||
return {
|
||||
title: document.getElementById('recipeTitle').value.trim(),
|
||||
description: document.getElementById('recipeDesc').value.trim(),
|
||||
image_url: currentRecipe ? currentRecipe.image_url : null,
|
||||
image_url: document.getElementById('imageUrlInput').value.trim() || (currentRecipe ? currentRecipe.image_url : ''),
|
||||
ingredients: ingredients,
|
||||
instructions: instructions,
|
||||
tags: tags,
|
||||
|
||||
Reference in New Issue
Block a user