fix: image upload (add extension field) + ingredient groups
- Fix Mealie image upload 422: send required `extension` field in form data - Parse ingredient groups from mindmegette (multiple div.ingredients containers with strong.ingredients-group titles) - Show group headers in UI with dashed-border accent input - Pass group markers through to Mealie as title-only ingredient entries Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -147,6 +147,16 @@ class MealieClient:
|
|||||||
ingredients = []
|
ingredients = []
|
||||||
for item in recipe.get("ingredients", []):
|
for item in recipe.get("ingredients", []):
|
||||||
if isinstance(item, dict):
|
if isinstance(item, dict):
|
||||||
|
# Group header marker
|
||||||
|
if "group" in item and "food" not in item:
|
||||||
|
ingredients.append({
|
||||||
|
"referenceId": str(uuid.uuid4()),
|
||||||
|
"title": item["group"],
|
||||||
|
"note": "",
|
||||||
|
"isFood": False,
|
||||||
|
"disableAmount": True,
|
||||||
|
})
|
||||||
|
else:
|
||||||
ingredients.append(self._build_ingredient(item))
|
ingredients.append(self._build_ingredient(item))
|
||||||
else:
|
else:
|
||||||
# Legacy: plain string
|
# Legacy: plain string
|
||||||
@@ -235,6 +245,7 @@ class MealieClient:
|
|||||||
r = self.session.put(
|
r = self.session.put(
|
||||||
f"{self.api_url}/api/recipes/{slug}/image",
|
f"{self.api_url}/api/recipes/{slug}/image",
|
||||||
files=files,
|
files=files,
|
||||||
|
data={"extension": ext},
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|||||||
+9
-2
@@ -61,9 +61,16 @@ def _parse_mindmegette(soup: BeautifulSoup, url: str) -> dict:
|
|||||||
image_url = _og(soup, "og:image")
|
image_url = _og(soup, "og:image")
|
||||||
|
|
||||||
# --- Ingredients ---
|
# --- Ingredients ---
|
||||||
|
# Multiple div.ingredients containers may exist (one per group).
|
||||||
|
# Group title: <strong class="ingredients-group">A habaráshoz:</strong>
|
||||||
ingredients = []
|
ingredients = []
|
||||||
ing_container = soup.find("div", class_="ingredients")
|
for ing_container in soup.find_all("div", class_="ingredients"):
|
||||||
if ing_container:
|
# Check for a group title
|
||||||
|
group_el = ing_container.find("strong", class_="ingredients-group")
|
||||||
|
group_name = _text(group_el).rstrip(":").strip() if group_el else ""
|
||||||
|
if group_name:
|
||||||
|
ingredients.append({"group": group_name})
|
||||||
|
|
||||||
for row in ing_container.find_all("div", class_="ingredients-meta"):
|
for row in ing_container.find_all("div", class_="ingredients-meta"):
|
||||||
# Actual HTML: <strong>qty</strong> <span>unit</span>
|
# Actual HTML: <strong>qty</strong> <span>unit</span>
|
||||||
# <a class="ingredients-link">name</a> <small>(extra)</small>
|
# <a class="ingredients-link">name</a> <small>(extra)</small>
|
||||||
|
|||||||
@@ -52,6 +52,31 @@
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
.ingredient-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0.8rem 0 0.3rem;
|
||||||
|
}
|
||||||
|
.ingredient-group input {
|
||||||
|
margin-bottom: 0;
|
||||||
|
flex: 1;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent);
|
||||||
|
border-style: dashed;
|
||||||
|
}
|
||||||
|
.ingredient-group button {
|
||||||
|
background: var(--danger);
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
.instruction-row {
|
.instruction-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
@@ -144,7 +169,10 @@
|
|||||||
<span class="col-extra">Megjegyzés</span>
|
<span class="col-extra">Megjegyzés</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="ingredientsList"></div>
|
<div id="ingredientsList"></div>
|
||||||
<button class="add-btn mt-1 mb-2" onclick="addIngredient({})">+ Hozzávaló hozzáadása</button>
|
<div class="flex gap-1 mt-1 mb-2">
|
||||||
|
<button class="add-btn" onclick="addIngredient({})">+ Hozzávaló</button>
|
||||||
|
<button class="add-btn" onclick="addIngredientGroup('')">+ Csoport</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Instructions -->
|
<!-- Instructions -->
|
||||||
<label>Elkészítés</label>
|
<label>Elkészítés</label>
|
||||||
@@ -236,6 +264,11 @@ function populatePreview(r) {
|
|||||||
|
|
||||||
function addIngredient(item) {
|
function addIngredient(item) {
|
||||||
if (typeof item === 'string') item = { food: item };
|
if (typeof item === 'string') item = { food: item };
|
||||||
|
// Group header marker
|
||||||
|
if (item.group !== undefined && item.food === undefined) {
|
||||||
|
addIngredientGroup(item.group);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const list = document.getElementById('ingredientsList');
|
const list = document.getElementById('ingredientsList');
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'ingredient-row';
|
row.className = 'ingredient-row';
|
||||||
@@ -247,6 +280,15 @@ function addIngredient(item) {
|
|||||||
list.appendChild(row);
|
list.appendChild(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addIngredientGroup(name) {
|
||||||
|
const list = document.getElementById('ingredientsList');
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'ingredient-group';
|
||||||
|
row.innerHTML = '<input type="text" class="ing-group-name" placeholder="Csoport neve" value="' + escHtml(name || '') + '">'
|
||||||
|
+ '<button onclick="this.parentElement.remove()">✕</button>';
|
||||||
|
list.appendChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
function addInstruction(value) {
|
function addInstruction(value) {
|
||||||
const list = document.getElementById('instructionsList');
|
const list = document.getElementById('instructionsList');
|
||||||
const idx = list.children.length + 1;
|
const idx = list.children.length + 1;
|
||||||
@@ -271,14 +313,19 @@ function renumberInstructions() {
|
|||||||
|
|
||||||
function gatherRecipe() {
|
function gatherRecipe() {
|
||||||
const ingredients = [];
|
const ingredients = [];
|
||||||
document.querySelectorAll('#ingredientsList .ingredient-row').forEach(row => {
|
document.querySelectorAll('#ingredientsList > div').forEach(el => {
|
||||||
const qty = row.querySelector('.ing-qty').value.trim();
|
if (el.classList.contains('ingredient-group')) {
|
||||||
const unit = row.querySelector('.ing-unit').value.trim();
|
const name = el.querySelector('.ing-group-name').value.trim();
|
||||||
const food = row.querySelector('.ing-food').value.trim();
|
if (name) ingredients.push({ group: name });
|
||||||
const extra = row.querySelector('.ing-extra').value.trim();
|
} else if (el.classList.contains('ingredient-row')) {
|
||||||
|
const qty = el.querySelector('.ing-qty').value.trim();
|
||||||
|
const unit = el.querySelector('.ing-unit').value.trim();
|
||||||
|
const food = el.querySelector('.ing-food').value.trim();
|
||||||
|
const extra = el.querySelector('.ing-extra').value.trim();
|
||||||
if (food || qty) {
|
if (food || qty) {
|
||||||
ingredients.push({ quantity: qty, unit: unit, food: food, extra: extra });
|
ingredients.push({ quantity: qty, unit: unit, food: food, extra: extra });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const instructions = [];
|
const instructions = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user