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:
+12
-1
@@ -147,7 +147,17 @@ class MealieClient:
|
||||
ingredients = []
|
||||
for item in recipe.get("ingredients", []):
|
||||
if isinstance(item, dict):
|
||||
ingredients.append(self._build_ingredient(item))
|
||||
# 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))
|
||||
else:
|
||||
# Legacy: plain string
|
||||
ingredients.append({
|
||||
@@ -235,6 +245,7 @@ class MealieClient:
|
||||
r = self.session.put(
|
||||
f"{self.api_url}/api/recipes/{slug}/image",
|
||||
files=files,
|
||||
data={"extension": ext},
|
||||
timeout=30,
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
+9
-2
@@ -61,9 +61,16 @@ def _parse_mindmegette(soup: BeautifulSoup, url: str) -> dict:
|
||||
image_url = _og(soup, "og:image")
|
||||
|
||||
# --- Ingredients ---
|
||||
# Multiple div.ingredients containers may exist (one per group).
|
||||
# Group title: <strong class="ingredients-group">A habaráshoz:</strong>
|
||||
ingredients = []
|
||||
ing_container = soup.find("div", class_="ingredients")
|
||||
if ing_container:
|
||||
for ing_container in soup.find_all("div", class_="ingredients"):
|
||||
# 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"):
|
||||
# Actual HTML: <strong>qty</strong> <span>unit</span>
|
||||
# <a class="ingredients-link">name</a> <small>(extra)</small>
|
||||
|
||||
@@ -52,6 +52,31 @@
|
||||
line-height: 1;
|
||||
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 {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
@@ -144,7 +169,10 @@
|
||||
<span class="col-extra">Megjegyzés</span>
|
||||
</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 -->
|
||||
<label>Elkészítés</label>
|
||||
@@ -236,6 +264,11 @@ function populatePreview(r) {
|
||||
|
||||
function addIngredient(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 row = document.createElement('div');
|
||||
row.className = 'ingredient-row';
|
||||
@@ -247,6 +280,15 @@ function addIngredient(item) {
|
||||
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) {
|
||||
const list = document.getElementById('instructionsList');
|
||||
const idx = list.children.length + 1;
|
||||
@@ -271,13 +313,18 @@ function renumberInstructions() {
|
||||
|
||||
function gatherRecipe() {
|
||||
const ingredients = [];
|
||||
document.querySelectorAll('#ingredientsList .ingredient-row').forEach(row => {
|
||||
const qty = row.querySelector('.ing-qty').value.trim();
|
||||
const unit = row.querySelector('.ing-unit').value.trim();
|
||||
const food = row.querySelector('.ing-food').value.trim();
|
||||
const extra = row.querySelector('.ing-extra').value.trim();
|
||||
if (food || qty) {
|
||||
ingredients.push({ quantity: qty, unit: unit, food: food, extra: extra });
|
||||
document.querySelectorAll('#ingredientsList > div').forEach(el => {
|
||||
if (el.classList.contains('ingredient-group')) {
|
||||
const name = el.querySelector('.ing-group-name').value.trim();
|
||||
if (name) ingredients.push({ group: name });
|
||||
} 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) {
|
||||
ingredients.push({ quantity: qty, unit: unit, food: food, extra: extra });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user