diff --git a/src/lib/components/BaseRecipeSelector.svelte b/src/lib/components/BaseRecipeSelector.svelte
new file mode 100644
index 00000000..18f971a6
--- /dev/null
+++ b/src/lib/components/BaseRecipeSelector.svelte
@@ -0,0 +1,249 @@
+
+
+
+
+
diff --git a/src/lib/components/CreateIngredientList.svelte b/src/lib/components/CreateIngredientList.svelte
index 1c863bce..02c560e7 100644
--- a/src/lib/components/CreateIngredientList.svelte
+++ b/src/lib/components/CreateIngredientList.svelte
@@ -10,6 +10,7 @@ import "$lib/css/action_button.css"
import { do_on_key } from '$lib/components/do_on_key.js'
import { portions } from '$lib/js/portions_store.js'
+import BaseRecipeSelector from '$lib/components/BaseRecipeSelector.svelte'
let portions_local
portions.subscribe((p) => {
@@ -43,6 +44,122 @@ let edit_heading = {
list_index: "",
}
+// Base recipe selector state
+let showSelector = false;
+let insertPosition = 0;
+
+// State for adding items to references
+let addingToReference = {
+ active: false,
+ list_index: -1,
+ position: 'before' as 'before' | 'after',
+ editing: false,
+ item_index: -1
+};
+
+function openSelector(position: number) {
+ insertPosition = position;
+ showSelector = true;
+}
+
+function handleSelect(recipe: any, options: any) {
+ const reference = {
+ type: 'reference',
+ name: options.labelOverride || (options.showLabel ? recipe.name : ''),
+ baseRecipeRef: recipe._id,
+ includeIngredients: options.includeIngredients,
+ showLabel: options.showLabel,
+ labelOverride: options.labelOverride || '',
+ itemsBefore: [],
+ itemsAfter: []
+ };
+
+ ingredients.splice(insertPosition, 0, reference);
+ ingredients = ingredients;
+ showSelector = false;
+}
+
+export function removeReference(list_index: number) {
+ const confirmed = confirm("Bist du dir sicher, dass du diese Referenz löschen möchtest?");
+ if (confirmed) {
+ ingredients.splice(list_index, 1);
+ ingredients = ingredients;
+ }
+}
+
+// Functions to manage items before/after base recipe in references
+function addItemToReference(list_index: number, position: 'before' | 'after', item: any) {
+ if (!ingredients[list_index].itemsBefore) ingredients[list_index].itemsBefore = [];
+ if (!ingredients[list_index].itemsAfter) ingredients[list_index].itemsAfter = [];
+
+ if (position === 'before') {
+ ingredients[list_index].itemsBefore.push(item);
+ } else {
+ ingredients[list_index].itemsAfter.push(item);
+ }
+ ingredients = ingredients;
+}
+
+function removeItemFromReference(list_index: number, position: 'before' | 'after', item_index: number) {
+ if (position === 'before') {
+ ingredients[list_index].itemsBefore.splice(item_index, 1);
+ } else {
+ ingredients[list_index].itemsAfter.splice(item_index, 1);
+ }
+ ingredients = ingredients;
+}
+
+function editItemFromReference(list_index: number, position: 'before' | 'after', item_index: number) {
+ const items = position === 'before' ? ingredients[list_index].itemsBefore : ingredients[list_index].itemsAfter;
+ const item = items[item_index];
+
+ // Set up edit state
+ addingToReference = {
+ active: true,
+ list_index,
+ position,
+ editing: true,
+ item_index
+ };
+
+ edit_ingredient = {
+ amount: item.amount || "",
+ unit: item.unit || "",
+ name: item.name || "",
+ sublist: "",
+ list_index: "",
+ ingredient_index: "",
+ };
+
+ const modal_el = document.querySelector("#edit_ingredient_modal") as HTMLDialogElement;
+ if (modal_el) {
+ modal_el.showModal();
+ }
+}
+
+function openAddToReferenceModal(list_index: number, position: 'before' | 'after') {
+ addingToReference = {
+ active: true,
+ list_index,
+ position,
+ editing: false,
+ item_index: -1
+ };
+ // Clear and open the edit ingredient modal for adding
+ edit_ingredient = {
+ amount: "",
+ unit: "",
+ name: "",
+ sublist: "",
+ list_index: "",
+ ingredient_index: "",
+ };
+ const modal_el = document.querySelector("#edit_ingredient_modal") as HTMLDialogElement;
+ if (modal_el) {
+ modal_el.showModal();
+ }
+}
+
function get_sublist_index(sublist_name, list){
for(var i =0; i < list.length; i++){
if(list[i].name == sublist_name){
@@ -102,12 +219,55 @@ export function show_modal_edit_ingredient(list_index, ingredient_index){
modal_el.showModal();
}
export function edit_ingredient_and_close_modal(){
- ingredients[edit_ingredient.list_index].list[edit_ingredient.ingredient_index] = {
- amount: edit_ingredient.amount,
- unit: edit_ingredient.unit,
- name: edit_ingredient.name,
+ // Check if we're adding to or editing a reference
+ if (addingToReference.active) {
+ // Don't add empty ingredients
+ if (!edit_ingredient.name) {
+ addingToReference = {
+ active: false,
+ list_index: -1,
+ position: 'before',
+ editing: false,
+ item_index: -1
+ };
+ const modal_el = document.querySelector("#edit_ingredient_modal");
+ modal_el.close();
+ return;
+ }
+
+ const item = {
+ amount: edit_ingredient.amount,
+ unit: edit_ingredient.unit,
+ name: edit_ingredient.name
+ };
+
+ if (addingToReference.editing) {
+ // Edit existing item in reference
+ const items = addingToReference.position === 'before'
+ ? ingredients[addingToReference.list_index].itemsBefore
+ : ingredients[addingToReference.list_index].itemsAfter;
+ items[addingToReference.item_index] = item;
+ ingredients = ingredients;
+ } else {
+ // Add new item to reference
+ addItemToReference(addingToReference.list_index, addingToReference.position, item);
+ }
+ addingToReference = {
+ active: false,
+ list_index: -1,
+ position: 'before',
+ editing: false,
+ item_index: -1
+ };
+ } else {
+ // Normal edit behavior
+ ingredients[edit_ingredient.list_index].list[edit_ingredient.ingredient_index] = {
+ amount: edit_ingredient.amount,
+ unit: edit_ingredient.unit,
+ name: edit_ingredient.name,
+ }
+ ingredients[edit_ingredient.list_index].name = edit_ingredient.sublist
}
- ingredients[edit_ingredient.list_index].name = edit_ingredient.sublist
const modal_el = document.querySelector("#edit_ingredient_modal");
modal_el.close();
}
@@ -430,6 +590,66 @@ h3{
width: 100%;
text-align: left;
}
+
+/* Base recipe reference styles */
+.reference-container {
+ margin-block: 1em;
+ padding: 1em;
+ background-color: var(--nord14);
+ border-radius: 10px;
+ border: 2px solid var(--nord9);
+ box-shadow: 0 0 0.5em 0.1em rgba(0,0,0,0.2);
+}
+
+.reference-header {
+ display: flex;
+ align-items: center;
+ gap: 1em;
+ margin-bottom: 0.5em;
+}
+
+.reference-badge {
+ flex-grow: 1;
+ font-weight: bold;
+ color: var(--nord0);
+ font-size: 1.1rem;
+}
+
+@media (prefers-color-scheme: dark) {
+ .reference-container {
+ background-color: var(--nord1);
+ }
+ .reference-badge {
+ color: var(--nord6);
+ }
+}
+
+.insert-base-recipe-button {
+ margin-block: 1rem;
+ padding: 1em 2em;
+ font-size: 1.1rem;
+ border-radius: 1000px;
+ background-color: var(--nord9);
+ color: white;
+ border: none;
+ cursor: pointer;
+ transition: 200ms;
+ box-shadow: 0 0 0.5em 0.1em rgba(0,0,0,0.2);
+}
+
+.insert-base-recipe-button:hover {
+ transform: scale(1.05, 1.05);
+ box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
+}
+
+.add-to-reference-button {
+ color: white;
+}
+
+.add-to-reference-button:hover {
+ scale: 1.02 1.02 !important;
+ transform: scale(1.02) !important;
+}
@@ -438,32 +658,118 @@ h3{
Zutaten
{#each ingredients as list, list_index}
-
-
-
+ {#if list.type === 'reference'}
+
+
+
-
{:else}
- Leer
- {/if}
-
-
-
-
+
+
+
+
+
+ {#if list.name }
+ {list.name}
+ {:else}
+ Leer
+ {/if}
+
+
+
+
{#each list.list as ingredient, ingredient_index (ingredient_index)}
{/each}
-
+
+ {/if}
{/each}
+
+
+
openSelector(ingredients.length)}>
+
+ Basisrezept einfügen
+
@@ -522,3 +835,10 @@ h3{
+
+
+
diff --git a/src/lib/components/CreateStepList.svelte b/src/lib/components/CreateStepList.svelte
index d676ef55..2c4805c1 100644
--- a/src/lib/components/CreateStepList.svelte
+++ b/src/lib/components/CreateStepList.svelte
@@ -9,6 +9,7 @@ import '$lib/css/nordtheme.css'
import "$lib/css/action_button.css"
import { do_on_key } from '$lib/components/do_on_key.js'
+import BaseRecipeSelector from '$lib/components/BaseRecipeSelector.svelte'
const step_placeholder = "Kartoffeln schälen..."
export let instructions
@@ -24,6 +25,118 @@ let edit_heading = {
list_index: "",
}
+// Base recipe selector state
+let showSelector = false;
+let insertPosition = 0;
+
+// State for adding steps to references
+let addingToReference = {
+ active: false,
+ list_index: -1,
+ position: 'before' as 'before' | 'after',
+ editing: false,
+ step_index: -1
+};
+
+function openSelector(position: number) {
+ insertPosition = position;
+ showSelector = true;
+}
+
+function handleSelect(recipe: any, options: any) {
+ const reference = {
+ type: 'reference',
+ name: options.labelOverride || (options.showLabel ? recipe.name : ''),
+ baseRecipeRef: recipe._id,
+ includeInstructions: options.includeInstructions,
+ showLabel: options.showLabel,
+ labelOverride: options.labelOverride || '',
+ stepsBefore: [],
+ stepsAfter: []
+ };
+
+ instructions.splice(insertPosition, 0, reference);
+ instructions = instructions;
+ showSelector = false;
+}
+
+export function removeReference(list_index: number) {
+ const confirmed = confirm("Bist du dir sicher, dass du diese Referenz löschen möchtest?");
+ if (confirmed) {
+ instructions.splice(list_index, 1);
+ instructions = instructions;
+ }
+}
+
+// Functions to manage steps before/after base recipe in references
+function addStepToReference(list_index: number, position: 'before' | 'after', step: string) {
+ if (!instructions[list_index].stepsBefore) instructions[list_index].stepsBefore = [];
+ if (!instructions[list_index].stepsAfter) instructions[list_index].stepsAfter = [];
+
+ if (position === 'before') {
+ instructions[list_index].stepsBefore.push(step);
+ } else {
+ instructions[list_index].stepsAfter.push(step);
+ }
+ instructions = instructions;
+}
+
+function removeStepFromReference(list_index: number, position: 'before' | 'after', step_index: number) {
+ if (position === 'before') {
+ instructions[list_index].stepsBefore.splice(step_index, 1);
+ } else {
+ instructions[list_index].stepsAfter.splice(step_index, 1);
+ }
+ instructions = instructions;
+}
+
+function editStepFromReference(list_index: number, position: 'before' | 'after', step_index: number) {
+ const steps = position === 'before' ? instructions[list_index].stepsBefore : instructions[list_index].stepsAfter;
+ const step = steps[step_index];
+
+ // Set up edit state
+ addingToReference = {
+ active: true,
+ list_index,
+ position,
+ editing: true,
+ step_index
+ };
+
+ edit_step = {
+ step: step || "",
+ name: "",
+ list_index: 0,
+ step_index: 0,
+ };
+
+ const modal_el = document.querySelector("#edit_step_modal") as HTMLDialogElement;
+ if (modal_el) {
+ modal_el.showModal();
+ }
+}
+
+function openAddToReferenceModal(list_index: number, position: 'before' | 'after') {
+ addingToReference = {
+ active: true,
+ list_index,
+ position,
+ editing: false,
+ step_index: -1
+ };
+ // Clear and open the edit step modal for adding
+ edit_step = {
+ step: "",
+ name: "",
+ list_index: 0,
+ step_index: 0,
+ };
+ const modal_el = document.querySelector("#edit_step_modal") as HTMLDialogElement;
+ if (modal_el) {
+ modal_el.showModal();
+ }
+}
+
function get_sublist_index(sublist_name, list){
for(var i =0; i < list.length; i++){
if(list[i].name == sublist_name){
@@ -86,7 +199,44 @@ export function show_modal_edit_step(list_index, step_index){
}
export function edit_step_and_close_modal(){
- instructions[edit_step.list_index].steps[edit_step.step_index] = edit_step.step
+ // Check if we're adding to or editing a reference
+ if (addingToReference.active) {
+ // Don't add empty steps
+ if (!edit_step.step || edit_step.step.trim() === '') {
+ addingToReference = {
+ active: false,
+ list_index: -1,
+ position: 'before',
+ editing: false,
+ step_index: -1
+ };
+ const modal_el = document.querySelector("#edit_step_modal");
+ modal_el.close();
+ return;
+ }
+
+ if (addingToReference.editing) {
+ // Edit existing step in reference
+ const steps = addingToReference.position === 'before'
+ ? instructions[addingToReference.list_index].stepsBefore
+ : instructions[addingToReference.list_index].stepsAfter;
+ steps[addingToReference.step_index] = edit_step.step;
+ instructions = instructions;
+ } else {
+ // Add new step to reference
+ addStepToReference(addingToReference.list_index, addingToReference.position, edit_step.step);
+ }
+ addingToReference = {
+ active: false,
+ list_index: -1,
+ position: 'before',
+ editing: false,
+ step_index: -1
+ };
+ } else {
+ // Normal edit behavior
+ instructions[edit_step.list_index].steps[edit_step.step_index] = edit_step.step
+ }
const modal_el = document.querySelector("#edit_step_modal");
modal_el.close();
}
@@ -451,6 +601,66 @@ h3{
width: 100%;
text-align: left;
}
+
+/* Base recipe reference styles */
+.reference-container {
+ margin-block: 1em;
+ padding: 1em;
+ background-color: var(--nord14);
+ border-radius: 10px;
+ border: 2px solid var(--nord9);
+ box-shadow: 0 0 0.5em 0.1em rgba(0,0,0,0.2);
+}
+
+.reference-header {
+ display: flex;
+ align-items: center;
+ gap: 1em;
+ margin-bottom: 0.5em;
+}
+
+.reference-badge {
+ flex-grow: 1;
+ font-weight: bold;
+ color: var(--nord0);
+ font-size: 1.1rem;
+}
+
+@media (prefers-color-scheme: dark) {
+ .reference-container {
+ background-color: var(--nord1);
+ }
+ .reference-badge {
+ color: var(--nord6);
+ }
+}
+
+.insert-base-recipe-button {
+ margin-block: 1rem;
+ padding: 1em 2em;
+ font-size: 1.1rem;
+ border-radius: 1000px;
+ background-color: var(--nord9);
+ color: white;
+ border: none;
+ cursor: pointer;
+ transition: 200ms;
+ box-shadow: 0 0 0.5em 0.1em rgba(0,0,0,0.2);
+}
+
+.insert-base-recipe-button:hover {
+ transform: scale(1.05, 1.05);
+ box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
+}
+
+.add-to-reference-button {
+ color: white;
+}
+
+.add-to-reference-button:hover {
+ scale: 1.02 1.02 !important;
+ transform: scale(1.02) !important;
+}
@@ -483,27 +693,115 @@ h3{
Zubereitung
{#each instructions as list, list_index}
-
-
-
- show_modal_edit_subheading_step(list_index)} class="subheading-button">
- {#if list.name}
- {list.name}
+ {#if list.type === 'reference'}
+
+
+
+
+
+ {#if list.stepsBefore && list.stepsBefore.length > 0}
+
Zusätzliche Schritte davor:
+
+ {#each list.stepsBefore as step, step_index}
+ -
+
+
+
+
+
editStepFromReference(list_index, 'before', step_index)} class="step-button" style="flex-grow: 1;">
+ {@html step}
+
+
+
editStepFromReference(list_index, 'before', step_index)} aria-label="Schritt bearbeiten">
+
+
+
removeStepFromReference(list_index, 'before', step_index)} aria-label="Schritt entfernen">
+
+
+
+
+
+ {/each}
+
+ {/if}
+
openAddToReferenceModal(list_index, 'before')}>
+ Schritt davor hinzufügen
+
+
+
+
+ → Inhalt vom Basisrezept wird hier eingefügt ←
+
+
+
+
openAddToReferenceModal(list_index, 'after')}>
+ Schritt danach hinzufügen
+
+ {#if list.stepsAfter && list.stepsAfter.length > 0}
+
Zusätzliche Schritte danach:
+
+ {#each list.stepsAfter as step, step_index}
+ -
+
+
+
+
+
editStepFromReference(list_index, 'after', step_index)} class="step-button" style="flex-grow: 1;">
+ {@html step}
+
+
+
editStepFromReference(list_index, 'after', step_index)} aria-label="Schritt bearbeiten">
+
+
+
removeStepFromReference(list_index, 'after', step_index)} aria-label="Schritt entfernen">
+
+
+
+
+
+ {/each}
+
+ {/if}
+
{:else}
- Leer
- {/if}
-
-
-
-
-
+
+
+
+ show_modal_edit_subheading_step(list_index)} class="subheading-button">
+ {#if list.name}
+ {list.name}
+ {:else}
+ Leer
+ {/if}
+
+
+
+
+
@@ -532,7 +830,14 @@ h3{
{/each}
+ {/if}
{/each}
+
+
+ openSelector(instructions.length)}>
+
+ Basisrezept einfügen
+
@@ -566,3 +871,10 @@ h3{
+
+
+
diff --git a/src/lib/components/IngredientsPage.svelte b/src/lib/components/IngredientsPage.svelte
index 99c8bf80..051dc31e 100644
--- a/src/lib/components/IngredientsPage.svelte
+++ b/src/lib/components/IngredientsPage.svelte
@@ -6,6 +6,65 @@ import { page } from '$app/stores';
import HefeSwapper from './HefeSwapper.svelte';
let { data } = $props();
+
+// Flatten ingredient references for display
+const flattenedIngredients = $derived.by(() => {
+ if (!data.ingredients) return [];
+
+ return data.ingredients.flatMap((item) => {
+ if (item.type === 'reference' && item.resolvedRecipe) {
+ const sections = [];
+
+ // Get translated or original ingredients
+ const lang = data.lang || 'de';
+ const ingredientsToUse = (lang === 'en' &&
+ item.resolvedRecipe.translations?.en?.ingredients)
+ ? item.resolvedRecipe.translations.en.ingredients
+ : item.resolvedRecipe.ingredients || [];
+
+ // Filter to only sections (not nested references)
+ const baseIngredients = item.includeIngredients
+ ? ingredientsToUse.filter(i => i.type === 'section' || !i.type)
+ : [];
+
+ // Combine all items into one section
+ const combinedList = [];
+
+ // Add items before
+ if (item.itemsBefore && item.itemsBefore.length > 0) {
+ combinedList.push(...item.itemsBefore);
+ }
+
+ // Add base recipe ingredients
+ baseIngredients.forEach(section => {
+ if (section.list) {
+ combinedList.push(...section.list);
+ }
+ });
+
+ // Add items after
+ if (item.itemsAfter && item.itemsAfter.length > 0) {
+ combinedList.push(...item.itemsAfter);
+ }
+
+ // Push as one section with optional label
+ if (combinedList.length > 0) {
+ sections.push({
+ type: 'section',
+ name: item.showLabel ? (item.labelOverride || item.resolvedRecipe.name) : '',
+ list: combinedList,
+ isReference: item.showLabel,
+ short_name: item.resolvedRecipe.short_name
+ });
+ }
+
+ return sections;
+ }
+
+ // Regular section - pass through
+ return [item];
+ });
+});
let multiplier = $state(data.multiplier || 1);
const isEnglish = $derived(data.lang === 'en');
@@ -324,6 +383,19 @@ span
background-color: var(--orange);
box-shadow: 0px 0px 0.5em 0.1em rgba(0,0,0, 0.3);
}
+
+/* Base recipe reference link styling */
+h3 a {
+ color: var(--nord11);
+ text-decoration: underline;
+ text-decoration-color: var(--nord11);
+}
+
+h3 a:hover {
+ color: var(--nord11);
+ text-decoration: underline;
+ text-decoration-color: var(--nord11);
+}
{#if data.ingredients}
@@ -400,10 +472,15 @@ span
{labels.ingredients}
-{#each data.ingredients as list, listIndex}
+{#each flattenedIngredients as list, listIndex}
{#if list.name}
- {list.name}
+ {#if list.isReference}
+
+ {:else}
+ {@html list.name}
+ {/if}
{/if}
+{#if list.list}
{#each list.list as item, ingredientIndex}
{@html adjust_amount(item.amount, multiplier)} {item.unit}
@@ -416,6 +493,7 @@ span
{/each}
+{/if}
{/each}
{/if}
diff --git a/src/lib/components/InstructionsPage.svelte b/src/lib/components/InstructionsPage.svelte
index f29076ac..d03fc11e 100644
--- a/src/lib/components/InstructionsPage.svelte
+++ b/src/lib/components/InstructionsPage.svelte
@@ -1,6 +1,65 @@