Cobb caught it. Mealie's web URL format is: https://recipes.sulkta.com/g/hayes-house/r/dairy-free-bread-recipe Was emitting /recipe/<slug> which 404s. Added _user_group_slug() helper that pulls .group.slug from /api/users/self (handles dict-or-string-or-fallback shapes for cross-version compat), threads the proper URL through both: - /api/recipes/<slug>.json (used by the modal) - /recipes/<slug> server-rendered detail (used as fallback for direct links) Falls back to the old /recipe/<slug> path if we can't resolve a group slug (won't break, will just 404 in Mealie if that ever happens).
92 lines
3 KiB
HTML
92 lines
3 KiB
HTML
{% extends "_base.html" %}
|
|
{% block title %}{{ recipe.name }} · Cauldron{% endblock %}
|
|
{% block content %}
|
|
|
|
<div class="page-head">
|
|
<div class="crumb">// recipes / {{ recipe.slug }}</div>
|
|
<h1>{{ recipe.name }}</h1>
|
|
{% if recipe.description %}<div class="lede">{{ recipe.description }}</div>{% endif %}
|
|
</div>
|
|
|
|
<section class="panel">
|
|
<div class="panel-head">
|
|
<h2>meta</h2>
|
|
</div>
|
|
<dl class="kv">
|
|
{% if recipe.totalTime %}<dt>total</dt><dd>{{ recipe.totalTime }}</dd>{% endif %}
|
|
{% if recipe.prepTime %}<dt>prep</dt><dd>{{ recipe.prepTime }}</dd>{% endif %}
|
|
{% if recipe.cookTime %}<dt>cook</dt><dd>{{ recipe.cookTime }}</dd>{% endif %}
|
|
{% if recipe.recipeYield %}<dt>yield</dt><dd>{{ recipe.recipeYield }}</dd>{% endif %}
|
|
{% if recipe.recipeServings %}<dt>servings</dt><dd>{{ recipe.recipeServings }}</dd>{% endif %}
|
|
</dl>
|
|
</section>
|
|
|
|
<section class="panel green">
|
|
<div class="panel-head">
|
|
<h2>ingredients</h2>
|
|
<span class="ctx">{{ (recipe.recipeIngredient or []) | length }} items</span>
|
|
</div>
|
|
{% if recipe.recipeIngredient %}
|
|
<ul style="list-style: none; padding: 0; margin: 0;">
|
|
{% for ing in recipe.recipeIngredient %}
|
|
<li style="padding: 4px 0; border-bottom: 1px dashed var(--line); color: var(--bone);">
|
|
{% if ing.display %}{{ ing.display }}{% else %}{{ ing.note or '' }}{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
<p class="muted">no ingredients listed.</p>
|
|
{% endif %}
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<div class="panel-head">
|
|
<h2>instructions</h2>
|
|
</div>
|
|
{% if recipe.recipeInstructions %}
|
|
<ol>
|
|
{% for step in recipe.recipeInstructions %}
|
|
<li style="margin: .8em 0; color: var(--bone);">{{ step.text }}</li>
|
|
{% endfor %}
|
|
</ol>
|
|
{% else %}
|
|
<p class="muted">no instructions listed.</p>
|
|
{% endif %}
|
|
</section>
|
|
|
|
<div class="btn-row">
|
|
<a class="btn btn-purple" href="/recipes">← back to grimoire</a>
|
|
<button id="pick-toggle" type="button" class="btn {% if picked %}btn-primary{% endif %}"
|
|
data-slug="{{ recipe.slug }}" data-name="{{ recipe.name }}"
|
|
onclick="togglePick(this)">
|
|
🍄 {% if picked %}pinned · remove{% else %}pin for ai plan{% endif %}
|
|
</button>
|
|
<a class="btn" href="{{ mealie_url }}" target="_blank" rel="noopener">view in mealie ↗</a>
|
|
</div>
|
|
|
|
<script>
|
|
async function togglePick(btn) {
|
|
const slug = btn.dataset.slug;
|
|
const name = btn.dataset.name;
|
|
const isOn = btn.classList.contains('btn-primary');
|
|
btn.disabled = true;
|
|
try {
|
|
const r = await fetch(`/api/picks/${encodeURIComponent(slug)}`, {
|
|
method: isOn ? 'DELETE' : 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: isOn ? null : JSON.stringify({ name })
|
|
});
|
|
if (!r.ok) throw new Error(r.status);
|
|
btn.classList.toggle('btn-primary');
|
|
btn.innerHTML = btn.classList.contains('btn-primary')
|
|
? '🍄 pinned · remove'
|
|
: '🍄 pin for ai plan';
|
|
} catch (e) {
|
|
/* leave as-is */
|
|
} finally {
|
|
btn.disabled = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
{% endblock %}
|