Compare commits
2 Commits
2696f09653
...
545bd97959
| Author | SHA1 | Date | |
|---|---|---|---|
|
545bd97959
|
|||
|
b67e2434b5
|
249
src/lib/components/BaseRecipeSelector.svelte
Normal file
249
src/lib/components/BaseRecipeSelector.svelte
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
import { do_on_key } from '$lib/components/do_on_key.js'
|
||||||
|
import Check from '$lib/assets/icons/Check.svelte'
|
||||||
|
|
||||||
|
export let type: 'ingredients' | 'instructions' = 'ingredients';
|
||||||
|
export let onSelect: (recipe: any, options: any) => void;
|
||||||
|
export let open = false;
|
||||||
|
|
||||||
|
// Unique dialog ID based on type to prevent conflicts when both are on the same page
|
||||||
|
const dialogId = `base-recipe-selector-modal-${type}`;
|
||||||
|
|
||||||
|
let baseRecipes: any[] = [];
|
||||||
|
let selectedRecipe: any = null;
|
||||||
|
let options = {
|
||||||
|
includeIngredients: false,
|
||||||
|
includeInstructions: false,
|
||||||
|
showLabel: true,
|
||||||
|
labelOverride: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset options whenever type or modal state changes
|
||||||
|
$: {
|
||||||
|
if (open || type) {
|
||||||
|
options.includeIngredients = type === 'ingredients';
|
||||||
|
options.includeInstructions = type === 'instructions';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const res = await fetch('/api/rezepte/base-recipes');
|
||||||
|
baseRecipes = await res.json();
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleInsert() {
|
||||||
|
if (selectedRecipe) {
|
||||||
|
onSelect(selectedRecipe, options);
|
||||||
|
// Reset modal
|
||||||
|
selectedRecipe = null;
|
||||||
|
options.labelOverride = '';
|
||||||
|
options.showLabel = true;
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
open = false;
|
||||||
|
if (browser) {
|
||||||
|
const modal = document.querySelector(`#${dialogId}`) as HTMLDialogElement;
|
||||||
|
if (modal) {
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openModal() {
|
||||||
|
if (browser) {
|
||||||
|
const modal = document.querySelector(`#${dialogId}`) as HTMLDialogElement;
|
||||||
|
if (modal) {
|
||||||
|
modal.showModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (browser) {
|
||||||
|
if (open) {
|
||||||
|
setTimeout(openModal, 0);
|
||||||
|
} else {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
dialog {
|
||||||
|
box-sizing: content-box;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
border: unset;
|
||||||
|
margin: 0;
|
||||||
|
transition: 500ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog[open]::backdrop {
|
||||||
|
animation: show 200ms ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes show {
|
||||||
|
from {
|
||||||
|
backdrop-filter: blur(0px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog h2 {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30vh;
|
||||||
|
margin-top: 30dvh;
|
||||||
|
filter: drop-shadow(0 0 0.4em black)
|
||||||
|
drop-shadow(0 0 1em black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-content {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-inline: auto;
|
||||||
|
margin-top: 2rem;
|
||||||
|
max-width: 600px;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: var(--blue);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 0 1em 0.2em rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-content label {
|
||||||
|
display: block;
|
||||||
|
margin-block: 1rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-content select,
|
||||||
|
.selector-content input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
border-radius: 1000px;
|
||||||
|
border: 2px solid var(--nord4);
|
||||||
|
background-color: white;
|
||||||
|
color: var(--nord0);
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: 100ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-content select:hover,
|
||||||
|
.selector-content select:focus,
|
||||||
|
.selector-content input[type="text"]:hover,
|
||||||
|
.selector-content input[type="text"]:focus {
|
||||||
|
border-color: var(--nord9);
|
||||||
|
transform: scale(1.02, 1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-content input[type="checkbox"] {
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.2em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group button {
|
||||||
|
padding: 0.75em 2em;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
border-radius: 1000px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 200ms;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-insert {
|
||||||
|
background-color: var(--nord14);
|
||||||
|
color: var(--nord0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-cancel {
|
||||||
|
background-color: var(--nord3);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group button:hover {
|
||||||
|
transform: scale(1.1, 1.1);
|
||||||
|
box-shadow: 0 0 1em 0.3em rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.selector-content {
|
||||||
|
background-color: var(--nord1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<dialog id={dialogId}>
|
||||||
|
<h2>Basisrezept einfügen</h2>
|
||||||
|
|
||||||
|
<div class="selector-content">
|
||||||
|
<label>
|
||||||
|
Basisrezept auswählen:
|
||||||
|
<select bind:value={selectedRecipe}>
|
||||||
|
<option value={null}>-- Auswählen --</option>
|
||||||
|
{#each baseRecipes as recipe}
|
||||||
|
<option value={recipe}>{recipe.icon} {recipe.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{#if type === 'ingredients'}
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={options.includeIngredients} />
|
||||||
|
Zutaten einbeziehen
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if type === 'instructions'}
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={options.includeInstructions} />
|
||||||
|
Zubereitungsschritte einbeziehen
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={options.showLabel} />
|
||||||
|
Rezeptname als Überschrift anzeigen
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{#if options.showLabel}
|
||||||
|
<label>
|
||||||
|
Eigene Überschrift (optional):
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={options.labelOverride}
|
||||||
|
placeholder={selectedRecipe?.name || 'Überschrift eingeben...'}
|
||||||
|
on:keydown={(event) => do_on_key(event, 'Enter', false, handleInsert)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="button-insert" on:click={handleInsert} disabled={!selectedRecipe}>
|
||||||
|
Einfügen
|
||||||
|
</button>
|
||||||
|
<button class="button-cancel" on:click={closeModal}>
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
@@ -10,6 +10,7 @@ import "$lib/css/action_button.css"
|
|||||||
|
|
||||||
import { do_on_key } from '$lib/components/do_on_key.js'
|
import { do_on_key } from '$lib/components/do_on_key.js'
|
||||||
import { portions } from '$lib/js/portions_store.js'
|
import { portions } from '$lib/js/portions_store.js'
|
||||||
|
import BaseRecipeSelector from '$lib/components/BaseRecipeSelector.svelte'
|
||||||
|
|
||||||
let portions_local
|
let portions_local
|
||||||
portions.subscribe((p) => {
|
portions.subscribe((p) => {
|
||||||
@@ -43,6 +44,122 @@ let edit_heading = {
|
|||||||
list_index: "",
|
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){
|
function get_sublist_index(sublist_name, list){
|
||||||
for(var i =0; i < list.length; i++){
|
for(var i =0; i < list.length; i++){
|
||||||
if(list[i].name == sublist_name){
|
if(list[i].name == sublist_name){
|
||||||
@@ -63,6 +180,17 @@ export function edit_subheading_and_close_modal(){
|
|||||||
el.close()
|
el.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleIngredientModalCancel() {
|
||||||
|
// Reset reference adding state when modal is cancelled (Escape key)
|
||||||
|
addingToReference = {
|
||||||
|
active: false,
|
||||||
|
list_index: -1,
|
||||||
|
position: 'before',
|
||||||
|
editing: false,
|
||||||
|
item_index: -1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function add_new_ingredient(){
|
export function add_new_ingredient(){
|
||||||
if(!new_ingredient.name){
|
if(!new_ingredient.name){
|
||||||
return
|
return
|
||||||
@@ -102,14 +230,62 @@ export function show_modal_edit_ingredient(list_index, ingredient_index){
|
|||||||
modal_el.showModal();
|
modal_el.showModal();
|
||||||
}
|
}
|
||||||
export function edit_ingredient_and_close_modal(){
|
export function edit_ingredient_and_close_modal(){
|
||||||
ingredients[edit_ingredient.list_index].list[edit_ingredient.ingredient_index] = {
|
// Check if we're adding to or editing a reference
|
||||||
amount: edit_ingredient.amount,
|
if (addingToReference.active) {
|
||||||
unit: edit_ingredient.unit,
|
// Don't add empty ingredients
|
||||||
name: edit_ingredient.name,
|
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") as HTMLDialogElement;
|
||||||
|
if (modal_el) {
|
||||||
|
setTimeout(() => modal_el.close(), 0);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
const modal_el = document.querySelector("#edit_ingredient_modal") as HTMLDialogElement;
|
||||||
|
if (modal_el) {
|
||||||
|
// Defer closing to next tick to ensure all bindings are updated
|
||||||
|
setTimeout(() => modal_el.close(), 0);
|
||||||
}
|
}
|
||||||
ingredients[edit_ingredient.list_index].name = edit_ingredient.sublist
|
|
||||||
const modal_el = document.querySelector("#edit_ingredient_modal");
|
|
||||||
modal_el.close();
|
|
||||||
}
|
}
|
||||||
export function update_list_position(list_index, direction){
|
export function update_list_position(list_index, direction){
|
||||||
if(direction == 1){
|
if(direction == 1){
|
||||||
@@ -430,6 +606,66 @@ h3{
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class=list_wrapper >
|
<div class=list_wrapper >
|
||||||
@@ -438,32 +674,118 @@ h3{
|
|||||||
|
|
||||||
<h2>Zutaten</h2>
|
<h2>Zutaten</h2>
|
||||||
{#each ingredients as list, list_index}
|
{#each ingredients as list, list_index}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
{#if list.type === 'reference'}
|
||||||
<h3>
|
<!-- Reference item display -->
|
||||||
<div class=move_buttons_container>
|
<div class="reference-container">
|
||||||
<button on:click="{() => update_list_position(list_index, 1)}" aria-label="Liste nach oben verschieben">
|
<div class="reference-header">
|
||||||
<svg class="button_arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6 1.41 1.41z"/></svg>
|
<div class="move_buttons_container">
|
||||||
</button>
|
<button on:click={() => update_list_position(list_index, 1)} aria-label="Referenz nach oben verschieben">
|
||||||
<button on:click="{() => update_list_position(list_index, -1)}" aria-label="Liste nach unten verschieben">
|
<svg class="button_arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6 1.41 1.41z"/></svg>
|
||||||
<svg class="button_arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
|
</button>
|
||||||
</button>
|
<button on:click={() => update_list_position(list_index, -1)} aria-label="Referenz nach unten verschieben">
|
||||||
</div>
|
<svg class="button_arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="reference-badge">
|
||||||
|
📋 Basisrezept: {list.name || 'Unbenannt'}
|
||||||
|
</div>
|
||||||
|
<div class="mod_icons">
|
||||||
|
<button class="action_button button_subtle" on:click={() => removeReference(list_index)} aria-label="Referenz entfernen">
|
||||||
|
<Cross fill="var(--nord11)"></Cross>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button on:click="{() => show_modal_edit_subheading_ingredient(list_index)}" class="subheading-button">
|
<!-- Items before base recipe -->
|
||||||
{#if list.name }
|
{#if list.itemsBefore && list.itemsBefore.length > 0}
|
||||||
{list.name}
|
<h4 style="margin-block: 0.5em; color: var(--nord9);">Zusätzliche Zutaten davor:</h4>
|
||||||
|
<div class="ingredients_grid">
|
||||||
|
{#each list.itemsBefore as item, item_index}
|
||||||
|
<div class=move_buttons_container>
|
||||||
|
<!-- Empty for consistency -->
|
||||||
|
</div>
|
||||||
|
<button on:click={() => editItemFromReference(list_index, 'before', item_index)} class="ingredient-amount-button">
|
||||||
|
{item.amount} {item.unit}
|
||||||
|
</button>
|
||||||
|
<button class="force_wrap ingredient-name-button" on:click={() => editItemFromReference(list_index, 'before', item_index)}>
|
||||||
|
{@html item.name}
|
||||||
|
</button>
|
||||||
|
<div class="mod_icons">
|
||||||
|
<button class="action_button button_subtle" on:click={() => editItemFromReference(list_index, 'before', item_index)} aria-label="Zutat bearbeiten">
|
||||||
|
<Pen fill="var(--nord6)" height="1em" width="1em"></Pen>
|
||||||
|
</button>
|
||||||
|
<button class="action_button button_subtle" on:click={() => removeItemFromReference(list_index, 'before', item_index)} aria-label="Zutat entfernen">
|
||||||
|
<Cross fill="var(--nord6)" height="1em" width="1em"></Cross>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<button class="action_button button_subtle add-to-reference-button" on:click={() => openAddToReferenceModal(list_index, 'before')}>
|
||||||
|
<Plus fill="var(--nord9)" height="1em" width="1em"></Plus> Zutat davor hinzufügen
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Base recipe content indicator -->
|
||||||
|
<div style="text-align: center; padding: 0.5em; margin: 0.5em 0; font-style: italic; color: var(--nord10); background-color: rgba(143, 188, 187, 0.4); border-radius: 5px;">
|
||||||
|
→ Inhalt vom Basisrezept wird hier eingefügt ←
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Items after base recipe -->
|
||||||
|
<button class="action_button button_subtle add-to-reference-button" on:click={() => openAddToReferenceModal(list_index, 'after')}>
|
||||||
|
<Plus fill="var(--nord9)" height="1em" width="1em"></Plus> Zutat danach hinzufügen
|
||||||
|
</button>
|
||||||
|
{#if list.itemsAfter && list.itemsAfter.length > 0}
|
||||||
|
<h4 style="margin-block: 0.5em; color: var(--nord9);">Zusätzliche Zutaten danach:</h4>
|
||||||
|
<div class="ingredients_grid">
|
||||||
|
{#each list.itemsAfter as item, item_index}
|
||||||
|
<div class=move_buttons_container>
|
||||||
|
<!-- Empty for consistency -->
|
||||||
|
</div>
|
||||||
|
<button on:click={() => editItemFromReference(list_index, 'after', item_index)} class="ingredient-amount-button">
|
||||||
|
{item.amount} {item.unit}
|
||||||
|
</button>
|
||||||
|
<button class="force_wrap ingredient-name-button" on:click={() => editItemFromReference(list_index, 'after', item_index)}>
|
||||||
|
{@html item.name}
|
||||||
|
</button>
|
||||||
|
<div class="mod_icons">
|
||||||
|
<button class="action_button button_subtle" on:click={() => editItemFromReference(list_index, 'after', item_index)} aria-label="Zutat bearbeiten">
|
||||||
|
<Pen fill="var(--nord6)" height="1em" width="1em"></Pen>
|
||||||
|
</button>
|
||||||
|
<button class="action_button button_subtle" on:click={() => removeItemFromReference(list_index, 'after', item_index)} aria-label="Zutat entfernen">
|
||||||
|
<Cross fill="var(--nord6)" height="1em" width="1em"></Cross>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
Leer
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
{/if}
|
<h3>
|
||||||
</button>
|
<div class=move_buttons_container>
|
||||||
<div class=mod_icons>
|
<button on:click="{() => update_list_position(list_index, 1)}" aria-label="Liste nach oben verschieben">
|
||||||
<button class="action_button button_subtle" on:click="{() => show_modal_edit_subheading_ingredient(list_index)}" aria-label="Überschrift bearbeiten">
|
<svg class="button_arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6 1.41 1.41z"/></svg>
|
||||||
<Pen fill=var(--nord1)></Pen> </button>
|
</button>
|
||||||
<button class="action_button button_subtle" on:click="{() => remove_list(list_index)}" aria-label="Liste entfernen">
|
<button on:click="{() => update_list_position(list_index, -1)}" aria-label="Liste nach unten verschieben">
|
||||||
<Cross fill=var(--nord1)></Cross></button>
|
<svg class="button_arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
|
||||||
</div>
|
</button>
|
||||||
</h3>
|
</div>
|
||||||
<div class=ingredients_grid>
|
|
||||||
|
<button on:click="{() => show_modal_edit_subheading_ingredient(list_index)}" class="subheading-button">
|
||||||
|
{#if list.name }
|
||||||
|
{list.name}
|
||||||
|
{:else}
|
||||||
|
Leer
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
<div class=mod_icons>
|
||||||
|
<button class="action_button button_subtle" on:click="{() => show_modal_edit_subheading_ingredient(list_index)}" aria-label="Überschrift bearbeiten">
|
||||||
|
<Pen fill=var(--nord1)></Pen> </button>
|
||||||
|
<button class="action_button button_subtle" on:click="{() => remove_list(list_index)}" aria-label="Liste entfernen">
|
||||||
|
<Cross fill=var(--nord1)></Cross></button>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
<div class=ingredients_grid>
|
||||||
{#each list.list as ingredient, ingredient_index (ingredient_index)}
|
{#each list.list as ingredient, ingredient_index (ingredient_index)}
|
||||||
<div class=move_buttons_container>
|
<div class=move_buttons_container>
|
||||||
<button on:click="{() => update_ingredient_position(list_index, ingredient_index, 1)}" aria-label="Zutat nach oben verschieben">
|
<button on:click="{() => update_ingredient_position(list_index, ingredient_index, 1)}" aria-label="Zutat nach oben verschieben">
|
||||||
@@ -483,8 +805,15 @@ h3{
|
|||||||
<Pen fill=var(--nord1) height=1em width=1em></Pen></button>
|
<Pen fill=var(--nord1) height=1em width=1em></Pen></button>
|
||||||
<button class="action_button button_subtle" on:click="{() => remove_ingredient(list_index, ingredient_index)}" aria-label="Zutat entfernen"><Cross fill=var(--nord1) height=1em width=1em></Cross></button></div>
|
<button class="action_button button_subtle" on:click="{() => remove_ingredient(list_index, ingredient_index)}" aria-label="Zutat entfernen"><Cross fill=var(--nord1) height=1em width=1em></Cross></button></div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
<!-- Button to insert base recipe -->
|
||||||
|
<button class="insert-base-recipe-button" on:click={() => openSelector(ingredients.length)}>
|
||||||
|
<Plus fill="white" style="display: inline; width: 1.5em; height: 1.5em; vertical-align: middle;"></Plus>
|
||||||
|
Basisrezept einfügen
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="adder shadow">
|
<div class="adder shadow">
|
||||||
@@ -498,7 +827,7 @@ h3{
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dialog id=edit_ingredient_modal>
|
<dialog id=edit_ingredient_modal on:cancel={handleIngredientModalCancel}>
|
||||||
<h2>Zutat verändern</h2>
|
<h2>Zutat verändern</h2>
|
||||||
<div class=adder>
|
<div class=adder>
|
||||||
<input class=category type="text" bind:value={edit_ingredient.sublist} placeholder="Kategorie (optional)">
|
<input class=category type="text" bind:value={edit_ingredient.sublist} placeholder="Kategorie (optional)">
|
||||||
@@ -522,3 +851,10 @@ h3{
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<!-- Base recipe selector -->
|
||||||
|
<BaseRecipeSelector
|
||||||
|
type="ingredients"
|
||||||
|
onSelect={handleSelect}
|
||||||
|
bind:open={showSelector}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import '$lib/css/nordtheme.css'
|
|||||||
import "$lib/css/action_button.css"
|
import "$lib/css/action_button.css"
|
||||||
|
|
||||||
import { do_on_key } from '$lib/components/do_on_key.js'
|
import { do_on_key } from '$lib/components/do_on_key.js'
|
||||||
|
import BaseRecipeSelector from '$lib/components/BaseRecipeSelector.svelte'
|
||||||
|
|
||||||
const step_placeholder = "Kartoffeln schälen..."
|
const step_placeholder = "Kartoffeln schälen..."
|
||||||
export let instructions
|
export let instructions
|
||||||
@@ -24,6 +25,118 @@ let edit_heading = {
|
|||||||
list_index: "",
|
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){
|
function get_sublist_index(sublist_name, list){
|
||||||
for(var i =0; i < list.length; i++){
|
for(var i =0; i < list.length; i++){
|
||||||
if(list[i].name == sublist_name){
|
if(list[i].name == sublist_name){
|
||||||
@@ -86,9 +199,51 @@ export function show_modal_edit_step(list_index, step_index){
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function edit_step_and_close_modal(){
|
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
|
||||||
const modal_el = document.querySelector("#edit_step_modal");
|
if (addingToReference.active) {
|
||||||
modal_el.close();
|
// 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") as HTMLDialogElement;
|
||||||
|
if (modal_el) {
|
||||||
|
setTimeout(() => modal_el.close(), 0);
|
||||||
|
}
|
||||||
|
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") as HTMLDialogElement;
|
||||||
|
if (modal_el) {
|
||||||
|
// Defer closing to next tick to ensure all bindings are updated
|
||||||
|
setTimeout(() => modal_el.close(), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function show_modal_edit_subheading_step(list_index){
|
export function show_modal_edit_subheading_step(list_index){
|
||||||
@@ -104,6 +259,17 @@ export function edit_subheading_steps_and_close_modal(){
|
|||||||
modal_el.close();
|
modal_el.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleStepModalCancel() {
|
||||||
|
// Reset reference adding state when modal is cancelled (Escape key)
|
||||||
|
addingToReference = {
|
||||||
|
active: false,
|
||||||
|
list_index: -1,
|
||||||
|
position: 'before',
|
||||||
|
editing: false,
|
||||||
|
step_index: -1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function clear_step(){
|
export function clear_step(){
|
||||||
const el = document.querySelector("#step")
|
const el = document.querySelector("#step")
|
||||||
@@ -451,6 +617,66 @@ h3{
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class=instructions>
|
<div class=instructions>
|
||||||
@@ -483,27 +709,115 @@ h3{
|
|||||||
|
|
||||||
<h2>Zubereitung</h2>
|
<h2>Zubereitung</h2>
|
||||||
{#each instructions as list, list_index}
|
{#each instructions as list, list_index}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
{#if list.type === 'reference'}
|
||||||
<h3>
|
<!-- Reference item display -->
|
||||||
<div class=move_buttons_container>
|
<div class="reference-container">
|
||||||
<button on:click="{() => update_list_position(list_index, 1)}" aria-label="Liste nach oben verschieben">
|
<div class="reference-header">
|
||||||
<svg class=button_arrow xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6 1.41 1.41z"/></svg>
|
<div class="move_buttons_container">
|
||||||
</button>
|
<button on:click={() => update_list_position(list_index, 1)} aria-label="Referenz nach oben verschieben">
|
||||||
<button on:click="{() => update_list_position(list_index, -1)}" aria-label="Liste nach unten verschieben">
|
<svg class="button_arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6 1.41 1.41z"/></svg>
|
||||||
<svg class=button_arrow xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
|
</button>
|
||||||
</button>
|
<button on:click={() => update_list_position(list_index, -1)} aria-label="Referenz nach unten verschieben">
|
||||||
</div>
|
<svg class="button_arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
|
||||||
<button on:click={() => show_modal_edit_subheading_step(list_index)} class="subheading-button">
|
</button>
|
||||||
{#if list.name}
|
</div>
|
||||||
{list.name}
|
<div class="reference-badge">
|
||||||
|
📋 Basisrezept: {list.name || 'Unbenannt'}
|
||||||
|
</div>
|
||||||
|
<div class="mod_icons">
|
||||||
|
<button class="action_button button_subtle" on:click={() => removeReference(list_index)} aria-label="Referenz entfernen">
|
||||||
|
<Cross fill="var(--nord11)"></Cross>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Steps before base recipe -->
|
||||||
|
{#if list.stepsBefore && list.stepsBefore.length > 0}
|
||||||
|
<h4 style="margin-block: 0.5em; color: var(--nord9);">Zusätzliche Schritte davor:</h4>
|
||||||
|
<ol>
|
||||||
|
{#each list.stepsBefore as step, step_index}
|
||||||
|
<li>
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<div class="move_buttons_container step_move_buttons">
|
||||||
|
<!-- Empty for consistency -->
|
||||||
|
</div>
|
||||||
|
<button on:click={() => editStepFromReference(list_index, 'before', step_index)} class="step-button" style="flex-grow: 1;">
|
||||||
|
{@html step}
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<button class="action_button button_subtle" on:click={() => editStepFromReference(list_index, 'before', step_index)} aria-label="Schritt bearbeiten">
|
||||||
|
<Pen fill="var(--nord6)" height="1em" width="1em"></Pen>
|
||||||
|
</button>
|
||||||
|
<button class="action_button button_subtle" on:click={() => removeStepFromReference(list_index, 'before', step_index)} aria-label="Schritt entfernen">
|
||||||
|
<Cross fill="var(--nord6)" height="1em" width="1em"></Cross>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ol>
|
||||||
|
{/if}
|
||||||
|
<button class="action_button button_subtle add-to-reference-button" on:click={() => openAddToReferenceModal(list_index, 'before')}>
|
||||||
|
<Plus fill="var(--nord9)" height="1em" width="1em"></Plus> Schritt davor hinzufügen
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Base recipe content indicator -->
|
||||||
|
<div style="text-align: center; padding: 0.5em; margin: 0.5em 0; font-style: italic; color: var(--nord10); background-color: rgba(143, 188, 187, 0.4); border-radius: 5px;">
|
||||||
|
→ Inhalt vom Basisrezept wird hier eingefügt ←
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Steps after base recipe -->
|
||||||
|
<button class="action_button button_subtle add-to-reference-button" on:click={() => openAddToReferenceModal(list_index, 'after')}>
|
||||||
|
<Plus fill="var(--nord9)" height="1em" width="1em"></Plus> Schritt danach hinzufügen
|
||||||
|
</button>
|
||||||
|
{#if list.stepsAfter && list.stepsAfter.length > 0}
|
||||||
|
<h4 style="margin-block: 0.5em; color: var(--nord9);">Zusätzliche Schritte danach:</h4>
|
||||||
|
<ol>
|
||||||
|
{#each list.stepsAfter as step, step_index}
|
||||||
|
<li>
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<div class="move_buttons_container step_move_buttons">
|
||||||
|
<!-- Empty for consistency -->
|
||||||
|
</div>
|
||||||
|
<button on:click={() => editStepFromReference(list_index, 'after', step_index)} class="step-button" style="flex-grow: 1;">
|
||||||
|
{@html step}
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<button class="action_button button_subtle" on:click={() => editStepFromReference(list_index, 'after', step_index)} aria-label="Schritt bearbeiten">
|
||||||
|
<Pen fill="var(--nord6)" height="1em" width="1em"></Pen>
|
||||||
|
</button>
|
||||||
|
<button class="action_button button_subtle" on:click={() => removeStepFromReference(list_index, 'after', step_index)} aria-label="Schritt entfernen">
|
||||||
|
<Cross fill="var(--nord6)" height="1em" width="1em"></Cross>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ol>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
Leer
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
{/if}
|
<h3>
|
||||||
</button>
|
<div class=move_buttons_container>
|
||||||
<button class="action_button button_subtle" on:click="{() => show_modal_edit_subheading_step(list_index)}" aria-label="Überschrift bearbeiten">
|
<button on:click="{() => update_list_position(list_index, 1)}" aria-label="Liste nach oben verschieben">
|
||||||
<Pen fill=var(--nord1)></Pen> </button>
|
<svg class=button_arrow xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6 1.41 1.41z"/></svg>
|
||||||
<button class="action_button button_subtle" on:click="{() => remove_list(list_index)}" aria-label="Liste entfernen">
|
</button>
|
||||||
<Cross fill=var(--nord1)></Cross>
|
<button on:click="{() => update_list_position(list_index, -1)}" aria-label="Liste nach unten verschieben">
|
||||||
|
<svg class=button_arrow xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16px" height="16px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button on:click={() => show_modal_edit_subheading_step(list_index)} class="subheading-button">
|
||||||
|
{#if list.name}
|
||||||
|
{list.name}
|
||||||
|
{:else}
|
||||||
|
Leer
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
<button class="action_button button_subtle" on:click="{() => show_modal_edit_subheading_step(list_index)}" aria-label="Überschrift bearbeiten">
|
||||||
|
<Pen fill=var(--nord1)></Pen> </button>
|
||||||
|
<button class="action_button button_subtle" on:click="{() => remove_list(list_index)}" aria-label="Liste entfernen">
|
||||||
|
<Cross fill=var(--nord1)></Cross>
|
||||||
</button>
|
</button>
|
||||||
</h3>
|
</h3>
|
||||||
<ol>
|
<ol>
|
||||||
@@ -532,7 +846,14 @@ h3{
|
|||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ol>
|
</ol>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
<!-- Button to insert base recipe -->
|
||||||
|
<button class="insert-base-recipe-button" on:click={() => openSelector(instructions.length)}>
|
||||||
|
<Plus fill="white" style="display: inline; width: 1.5em; height: 1.5em; vertical-align: middle;"></Plus>
|
||||||
|
Basisrezept einfügen
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class='adder shadow'>
|
<div class='adder shadow'>
|
||||||
@@ -545,7 +866,7 @@ h3{
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dialog id=edit_step_modal>
|
<dialog id=edit_step_modal on:cancel={handleStepModalCancel}>
|
||||||
<h2>Schritt verändern</h2>
|
<h2>Schritt verändern</h2>
|
||||||
<div class=adder>
|
<div class=adder>
|
||||||
<input class=category type="text" bind:value={edit_step.name} placeholder="Unterkategorie (optional)" on:keydown={(event) => do_on_key(event, 'Enter', false , edit_step_and_close_modal)}>
|
<input class=category type="text" bind:value={edit_step.name} placeholder="Unterkategorie (optional)" on:keydown={(event) => do_on_key(event, 'Enter', false , edit_step_and_close_modal)}>
|
||||||
@@ -566,3 +887,10 @@ h3{
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<!-- Base recipe selector -->
|
||||||
|
<BaseRecipeSelector
|
||||||
|
type="instructions"
|
||||||
|
onSelect={handleSelect}
|
||||||
|
bind:open={showSelector}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -6,6 +6,65 @@ import { page } from '$app/stores';
|
|||||||
import HefeSwapper from './HefeSwapper.svelte';
|
import HefeSwapper from './HefeSwapper.svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
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);
|
let multiplier = $state(data.multiplier || 1);
|
||||||
|
|
||||||
const isEnglish = $derived(data.lang === 'en');
|
const isEnglish = $derived(data.lang === 'en');
|
||||||
@@ -324,6 +383,19 @@ span
|
|||||||
background-color: var(--orange);
|
background-color: var(--orange);
|
||||||
box-shadow: 0px 0px 0.5em 0.1em rgba(0,0,0, 0.3);
|
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);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{#if data.ingredients}
|
{#if data.ingredients}
|
||||||
<div class=ingredients>
|
<div class=ingredients>
|
||||||
@@ -400,10 +472,15 @@ span
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>{labels.ingredients}</h2>
|
<h2>{labels.ingredients}</h2>
|
||||||
{#each data.ingredients as list, listIndex}
|
{#each flattenedIngredients as list, listIndex}
|
||||||
{#if list.name}
|
{#if list.name}
|
||||||
<h3>{list.name}</h3>
|
{#if list.isReference}
|
||||||
|
<h3><a href="{list.short_name}?multiplier={multiplier}">{@html list.name}</a></h3>
|
||||||
|
{:else}
|
||||||
|
<h3>{@html list.name}</h3>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if list.list}
|
||||||
<div class=ingredients_grid>
|
<div class=ingredients_grid>
|
||||||
{#each list.list as item, ingredientIndex}
|
{#each list.list as item, ingredientIndex}
|
||||||
<div class=amount>{@html adjust_amount(item.amount, multiplier)} {item.unit}</div>
|
<div class=amount>{@html adjust_amount(item.amount, multiplier)} {item.unit}</div>
|
||||||
@@ -416,6 +493,7 @@ span
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,6 +1,65 @@
|
|||||||
<script>
|
<script>
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
let multiplier = $state(data.multiplier || 1);
|
||||||
|
|
||||||
|
// Flatten instruction references for display
|
||||||
|
const flattenedInstructions = $derived.by(() => {
|
||||||
|
if (!data.instructions) return [];
|
||||||
|
|
||||||
|
return data.instructions.flatMap((item) => {
|
||||||
|
if (item.type === 'reference' && item.resolvedRecipe) {
|
||||||
|
// Get translated or original instructions
|
||||||
|
const lang = data.lang || 'de';
|
||||||
|
const instructionsToUse = (lang === 'en' &&
|
||||||
|
item.resolvedRecipe.translations?.en?.instructions)
|
||||||
|
? item.resolvedRecipe.translations.en.instructions
|
||||||
|
: item.resolvedRecipe.instructions || [];
|
||||||
|
|
||||||
|
// Filter to only sections (not nested references)
|
||||||
|
const baseInstructions = item.includeInstructions
|
||||||
|
? instructionsToUse.filter(i => i.type === 'section' || !i.type)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Combine all steps into one section
|
||||||
|
const combinedSteps = [];
|
||||||
|
|
||||||
|
// Add steps before
|
||||||
|
if (item.stepsBefore && item.stepsBefore.length > 0) {
|
||||||
|
combinedSteps.push(...item.stepsBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add base recipe instructions
|
||||||
|
baseInstructions.forEach(section => {
|
||||||
|
if (section.steps) {
|
||||||
|
combinedSteps.push(...section.steps);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add steps after
|
||||||
|
if (item.stepsAfter && item.stepsAfter.length > 0) {
|
||||||
|
combinedSteps.push(...item.stepsAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push as one section with optional label
|
||||||
|
if (combinedSteps.length > 0) {
|
||||||
|
return [{
|
||||||
|
type: 'section',
|
||||||
|
name: item.showLabel ? (item.labelOverride || item.resolvedRecipe.name) : '',
|
||||||
|
steps: combinedSteps,
|
||||||
|
isReference: item.showLabel,
|
||||||
|
short_name: item.resolvedRecipe.short_name
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular section - pass through
|
||||||
|
return [item];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const isEnglish = $derived(data.lang === 'en');
|
const isEnglish = $derived(data.lang === 'en');
|
||||||
const labels = $derived({
|
const labels = $derived({
|
||||||
preparation: isEnglish ? 'Preparation:' : 'Vorbereitung:',
|
preparation: isEnglish ? 'Preparation:' : 'Vorbereitung:',
|
||||||
@@ -67,6 +126,19 @@ ol li::marker{
|
|||||||
h4{
|
h4{
|
||||||
margin-block: 0;
|
margin-block: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class=instructions>
|
<div class=instructions>
|
||||||
<div class=additional_info>
|
<div class=additional_info>
|
||||||
@@ -100,15 +172,21 @@ h4{
|
|||||||
|
|
||||||
{#if data.instructions}
|
{#if data.instructions}
|
||||||
<h2>{labels.instructions}</h2>
|
<h2>{labels.instructions}</h2>
|
||||||
{#each data.instructions as list}
|
{#each flattenedInstructions as list}
|
||||||
{#if list.name}
|
{#if list.name}
|
||||||
<h3>{list.name}</h3>
|
{#if list.isReference}
|
||||||
|
<h3><a href="{list.short_name}?multiplier={multiplier}">{@html list.name}</a></h3>
|
||||||
|
{:else}
|
||||||
|
<h3>{@html list.name}</h3>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if list.steps}
|
||||||
<ol>
|
<ol>
|
||||||
{#each list.steps as step}
|
{#each list.steps as step}
|
||||||
<li>{@html step}</li>
|
<li>{@html step}</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ol>
|
</ol>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,17 +29,56 @@ const RecipeSchema = new mongoose.Schema(
|
|||||||
portions :{type:String, default: ""},
|
portions :{type:String, default: ""},
|
||||||
cooking: {type:String, default: ""},
|
cooking: {type:String, default: ""},
|
||||||
total_time : {type:String, default: ""},
|
total_time : {type:String, default: ""},
|
||||||
ingredients : [ { name: {type: String, default: ""},
|
ingredients: [{
|
||||||
list: [{name: {type: String, default: ""},
|
// Common fields
|
||||||
unit: String,
|
name: { type: String, default: "" },
|
||||||
amount: String,
|
type: { type: String, enum: ['section', 'reference'], default: 'section' },
|
||||||
}]
|
|
||||||
|
// For type='section' (existing structure)
|
||||||
|
list: [{
|
||||||
|
name: { type: String, default: "" },
|
||||||
|
unit: String,
|
||||||
|
amount: String,
|
||||||
|
}],
|
||||||
|
|
||||||
|
// For type='reference' (new base recipe references)
|
||||||
|
baseRecipeRef: { type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' },
|
||||||
|
includeIngredients: { type: Boolean, default: true },
|
||||||
|
showLabel: { type: Boolean, default: true },
|
||||||
|
labelOverride: { type: String, default: "" },
|
||||||
|
itemsBefore: [{
|
||||||
|
name: { type: String, default: "" },
|
||||||
|
unit: String,
|
||||||
|
amount: String,
|
||||||
|
}],
|
||||||
|
itemsAfter: [{
|
||||||
|
name: { type: String, default: "" },
|
||||||
|
unit: String,
|
||||||
|
amount: String,
|
||||||
|
}],
|
||||||
}],
|
}],
|
||||||
instructions : [{name: {type: String, default: ""},
|
instructions: [{
|
||||||
steps: [String]}],
|
// Common fields
|
||||||
|
name: { type: String, default: "" },
|
||||||
|
type: { type: String, enum: ['section', 'reference'], default: 'section' },
|
||||||
|
|
||||||
|
// For type='section' (existing structure)
|
||||||
|
steps: [String],
|
||||||
|
|
||||||
|
// For type='reference' (new base recipe references)
|
||||||
|
baseRecipeRef: { type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' },
|
||||||
|
includeInstructions: { type: Boolean, default: true },
|
||||||
|
showLabel: { type: Boolean, default: true },
|
||||||
|
labelOverride: { type: String, default: "" },
|
||||||
|
stepsBefore: [String],
|
||||||
|
stepsAfter: [String],
|
||||||
|
}],
|
||||||
preamble : String,
|
preamble : String,
|
||||||
addendum : String,
|
addendum : String,
|
||||||
|
|
||||||
|
// Base recipe flag
|
||||||
|
isBaseRecipe: {type: Boolean, default: false},
|
||||||
|
|
||||||
// English translations
|
// English translations
|
||||||
translations: {
|
translations: {
|
||||||
en: {
|
en: {
|
||||||
@@ -65,16 +104,38 @@ const RecipeSchema = new mongoose.Schema(
|
|||||||
final: {type: String},
|
final: {type: String},
|
||||||
},
|
},
|
||||||
ingredients: [{
|
ingredients: [{
|
||||||
name: {type: String, default: ""},
|
name: { type: String, default: "" },
|
||||||
|
type: { type: String, enum: ['section', 'reference'], default: 'section' },
|
||||||
list: [{
|
list: [{
|
||||||
name: {type: String, default: ""},
|
name: { type: String, default: "" },
|
||||||
unit: String,
|
unit: String,
|
||||||
amount: String,
|
amount: String,
|
||||||
}]
|
}],
|
||||||
|
baseRecipeRef: { type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' },
|
||||||
|
includeIngredients: { type: Boolean, default: true },
|
||||||
|
showLabel: { type: Boolean, default: true },
|
||||||
|
labelOverride: { type: String, default: "" },
|
||||||
|
itemsBefore: [{
|
||||||
|
name: { type: String, default: "" },
|
||||||
|
unit: String,
|
||||||
|
amount: String,
|
||||||
|
}],
|
||||||
|
itemsAfter: [{
|
||||||
|
name: { type: String, default: "" },
|
||||||
|
unit: String,
|
||||||
|
amount: String,
|
||||||
|
}],
|
||||||
}],
|
}],
|
||||||
instructions: [{
|
instructions: [{
|
||||||
name: {type: String, default: ""},
|
name: { type: String, default: "" },
|
||||||
steps: [String]
|
type: { type: String, enum: ['section', 'reference'], default: 'section' },
|
||||||
|
steps: [String],
|
||||||
|
baseRecipeRef: { type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' },
|
||||||
|
includeInstructions: { type: Boolean, default: true },
|
||||||
|
showLabel: { type: Boolean, default: true },
|
||||||
|
labelOverride: { type: String, default: "" },
|
||||||
|
stepsBefore: [String],
|
||||||
|
stepsAfter: [String],
|
||||||
}],
|
}],
|
||||||
images: [{
|
images: [{
|
||||||
alt: String,
|
alt: String,
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
let short_name = ""
|
let short_name = ""
|
||||||
let datecreated = new Date()
|
let datecreated = new Date()
|
||||||
let datemodified = datecreated
|
let datemodified = datecreated
|
||||||
|
let isBaseRecipe = false
|
||||||
|
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import CardAdd from '$lib/components/CardAdd.svelte';
|
import CardAdd from '$lib/components/CardAdd.svelte';
|
||||||
@@ -118,6 +119,7 @@
|
|||||||
ingredients,
|
ingredients,
|
||||||
preamble,
|
preamble,
|
||||||
addendum,
|
addendum,
|
||||||
|
isBaseRecipe,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +320,13 @@ button.action_button{
|
|||||||
<h3>Kurzname (für URL):</h3>
|
<h3>Kurzname (für URL):</h3>
|
||||||
<input bind:value={short_name} placeholder="Kurzname"/>
|
<input bind:value={short_name} placeholder="Kurzname"/>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin: 1rem;">
|
||||||
|
<label style="font-size: 1.1rem; cursor: pointer;">
|
||||||
|
<input type="checkbox" bind:checked={isBaseRecipe} style="width: auto; display: inline; margin-right: 0.5em;" />
|
||||||
|
Als Basisrezept markieren (kann von anderen Rezepten referenziert werden)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class=title_container>
|
<div class=title_container>
|
||||||
<div class=title>
|
<div class=title>
|
||||||
<h4>Eine etwas längere Beschreibung:</h4>
|
<h4>Eine etwas längere Beschreibung:</h4>
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
let short_name = data.recipe.short_name
|
let short_name = data.recipe.short_name
|
||||||
let datecreated = data.recipe.datecreated
|
let datecreated = data.recipe.datecreated
|
||||||
let datemodified = new Date()
|
let datemodified = new Date()
|
||||||
|
let isBaseRecipe = data.recipe.isBaseRecipe || false
|
||||||
|
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import CardAdd from '$lib/components/CardAdd.svelte';
|
import CardAdd from '$lib/components/CardAdd.svelte';
|
||||||
@@ -117,6 +118,7 @@
|
|||||||
addendum,
|
addendum,
|
||||||
preamble,
|
preamble,
|
||||||
note,
|
note,
|
||||||
|
isBaseRecipe,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +192,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function doDelete(){
|
async function doDelete(){
|
||||||
const response = confirm("Bist du dir sicher, dass du das Rezept löschen willst?")
|
// Check for references if this is a base recipe
|
||||||
|
const checkRes = await fetch(`/api/rezepte/check-references/${data.recipe._id}`);
|
||||||
|
const checkData = await checkRes.json();
|
||||||
|
|
||||||
|
let response;
|
||||||
|
if (checkData.isReferenced) {
|
||||||
|
const refList = checkData.references
|
||||||
|
.map(r => ` • ${r.name}`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
response = confirm(
|
||||||
|
`Dieses Rezept wird von folgenden Rezepten referenziert:\n\n${refList}\n\n` +
|
||||||
|
`Die Referenzen werden in regulären Inhalt umgewandelt.\n` +
|
||||||
|
`Möchtest du fortfahren?`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
response = confirm("Bist du dir sicher, dass du das Rezept löschen willst?");
|
||||||
|
}
|
||||||
|
|
||||||
if(!response){
|
if(!response){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -454,6 +474,42 @@ button.action_button{
|
|||||||
<h3>Kurzname (für URL):</h3>
|
<h3>Kurzname (für URL):</h3>
|
||||||
<input bind:value={short_name} placeholder="Kurzname"/>
|
<input bind:value={short_name} placeholder="Kurzname"/>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin: 1rem;">
|
||||||
|
<label style="font-size: 1.1rem; cursor: pointer;">
|
||||||
|
<input type="checkbox" bind:checked={isBaseRecipe} style="width: auto; display: inline; margin-right: 0.5em;" />
|
||||||
|
Als Basisrezept markieren (kann von anderen Rezepten referenziert werden)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if isBaseRecipe}
|
||||||
|
<div style="background-color: var(--nord14); padding: 1.5rem; margin: 1rem auto; max-width: 600px; border-radius: 10px; border: 2px solid var(--nord9);">
|
||||||
|
<h3 style="margin-top: 0; color: var(--nord0);">📋 Basisrezept-Informationen</h3>
|
||||||
|
{#await fetch(`/api/rezepte/check-references/${data.recipe._id}`).then(r => r.json())}
|
||||||
|
<p style="color: var(--nord3);">Lade Referenzen...</p>
|
||||||
|
{:then refData}
|
||||||
|
{#if refData.isReferenced}
|
||||||
|
<h4 style="color: var(--nord0);">Wird referenziert von:</h4>
|
||||||
|
<ul style="color: var(--nord1); list-style-position: inside;">
|
||||||
|
{#each refData.references as ref}
|
||||||
|
<li>
|
||||||
|
<a href="/{data.lang}/edit/{ref.short_name}" style="color: var(--nord10); font-weight: bold; text-decoration: underline;">
|
||||||
|
{ref.name}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
<p style="color: var(--nord11); font-weight: bold; margin-top: 1rem;">
|
||||||
|
⚠️ Änderungen an diesem Basisrezept wirken sich auf alle referenzierenden Rezepte aus.
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<p style="color: var(--nord3);">Dieses Basisrezept wird noch nicht referenziert.</p>
|
||||||
|
{/if}
|
||||||
|
{:catch error}
|
||||||
|
<p style="color: var(--nord11);">Fehler beim Laden der Referenzen.</p>
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class=title_container>
|
<div class=title_container>
|
||||||
<div class=title>
|
<div class=title>
|
||||||
<h4>Eine etwas längere Beschreibung:</h4>
|
<h4>Eine etwas längere Beschreibung:</h4>
|
||||||
|
|||||||
16
src/routes/api/rezepte/base-recipes/+server.ts
Normal file
16
src/routes/api/rezepte/base-recipes/+server.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import { Recipe } from '../../../../models/Recipe';
|
||||||
|
import { dbConnect } from '../../../../utils/db';
|
||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
// GET: List all base recipes for selector UI
|
||||||
|
export const GET: RequestHandler = async () => {
|
||||||
|
await dbConnect();
|
||||||
|
|
||||||
|
const baseRecipes = await Recipe.find({ isBaseRecipe: true })
|
||||||
|
.select('_id short_name name category icon')
|
||||||
|
.sort({ name: 1 })
|
||||||
|
.lean();
|
||||||
|
|
||||||
|
return json(baseRecipes);
|
||||||
|
};
|
||||||
21
src/routes/api/rezepte/check-references/[id]/+server.ts
Normal file
21
src/routes/api/rezepte/check-references/[id]/+server.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
import { Recipe } from '../../../../../models/Recipe';
|
||||||
|
import { dbConnect } from '../../../../../utils/db';
|
||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
// GET: Check which recipes reference this recipe
|
||||||
|
export const GET: RequestHandler = async ({ params }) => {
|
||||||
|
await dbConnect();
|
||||||
|
|
||||||
|
const referencingRecipes = await Recipe.find({
|
||||||
|
$or: [
|
||||||
|
{ 'ingredients.baseRecipeRef': params.id },
|
||||||
|
{ 'instructions.baseRecipeRef': params.id }
|
||||||
|
]
|
||||||
|
}).select('short_name name').lean();
|
||||||
|
|
||||||
|
return json({
|
||||||
|
isReferenced: referencingRecipes.length > 0,
|
||||||
|
references: referencingRecipes
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -21,6 +21,45 @@ export const POST: RequestHandler = async ({request, locals}) => {
|
|||||||
throw error(404, "Recipe not found");
|
throw error(404, "Recipe not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this recipe is referenced by others
|
||||||
|
const referencingRecipes = await Recipe.find({
|
||||||
|
$or: [
|
||||||
|
{ 'ingredients.baseRecipeRef': recipe._id },
|
||||||
|
{ 'instructions.baseRecipeRef': recipe._id }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expand all references into regular content before deletion
|
||||||
|
for (const depRecipe of referencingRecipes) {
|
||||||
|
// Expand ingredient references
|
||||||
|
if (depRecipe.ingredients) {
|
||||||
|
depRecipe.ingredients = depRecipe.ingredients.flatMap((item: any) => {
|
||||||
|
if (item.type === 'reference' && item.baseRecipeRef && item.baseRecipeRef.equals(recipe._id)) {
|
||||||
|
if (item.includeIngredients && recipe.ingredients) {
|
||||||
|
return recipe.ingredients.filter((i: any) => i.type === 'section' || !i.type);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [item];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand instruction references
|
||||||
|
if (depRecipe.instructions) {
|
||||||
|
depRecipe.instructions = depRecipe.instructions.flatMap((item: any) => {
|
||||||
|
if (item.type === 'reference' && item.baseRecipeRef && item.baseRecipeRef.equals(recipe._id)) {
|
||||||
|
if (item.includeInstructions && recipe.instructions) {
|
||||||
|
return recipe.instructions.filter((i: any) => i.type === 'section' || !i.type);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [item];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await depRecipe.save();
|
||||||
|
}
|
||||||
|
|
||||||
// Remove this recipe from all users' favorites
|
// Remove this recipe from all users' favorites
|
||||||
await UserFavorites.updateMany(
|
await UserFavorites.updateMany(
|
||||||
{ favorites: recipe._id },
|
{ favorites: recipe._id },
|
||||||
|
|||||||
@@ -6,11 +6,40 @@ import { error } from '@sveltejs/kit';
|
|||||||
|
|
||||||
export const GET: RequestHandler = async ({params}) => {
|
export const GET: RequestHandler = async ({params}) => {
|
||||||
await dbConnect();
|
await dbConnect();
|
||||||
let recipe = (await Recipe.findOne({ short_name: params.name}).lean()) as RecipeModelType[];
|
let recipe = await Recipe.findOne({ short_name: params.name})
|
||||||
|
.populate({
|
||||||
|
path: 'ingredients.baseRecipeRef',
|
||||||
|
select: 'short_name name ingredients translations'
|
||||||
|
})
|
||||||
|
.populate({
|
||||||
|
path: 'instructions.baseRecipeRef',
|
||||||
|
select: 'short_name name instructions translations'
|
||||||
|
})
|
||||||
|
.lean() as RecipeModelType[];
|
||||||
|
|
||||||
recipe = JSON.parse(JSON.stringify(recipe));
|
recipe = JSON.parse(JSON.stringify(recipe));
|
||||||
if(recipe == null){
|
if(recipe == null){
|
||||||
throw error(404, "Recipe not found")
|
throw error(404, "Recipe not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map populated refs to resolvedRecipe field
|
||||||
|
if (recipe?.ingredients) {
|
||||||
|
recipe.ingredients = recipe.ingredients.map((item: any) => {
|
||||||
|
if (item.type === 'reference' && item.baseRecipeRef) {
|
||||||
|
return { ...item, resolvedRecipe: item.baseRecipeRef };
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipe?.instructions) {
|
||||||
|
recipe.instructions = recipe.instructions.map((item: any) => {
|
||||||
|
if (item.type === 'reference' && item.baseRecipeRef) {
|
||||||
|
return { ...item, resolvedRecipe: item.baseRecipeRef };
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return json(recipe);
|
return json(recipe);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,74 @@ export type TranslationMetadata = {
|
|||||||
fieldsModifiedSinceTranslation?: string[];
|
fieldsModifiedSinceTranslation?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Ingredient discriminated union types
|
||||||
|
export type IngredientSection = {
|
||||||
|
name?: string;
|
||||||
|
type: 'section';
|
||||||
|
list: [{
|
||||||
|
name: string;
|
||||||
|
unit: string;
|
||||||
|
amount: string;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IngredientReference = {
|
||||||
|
name?: string;
|
||||||
|
type: 'reference';
|
||||||
|
baseRecipeRef: string; // ObjectId as string
|
||||||
|
includeIngredients: boolean;
|
||||||
|
showLabel: boolean;
|
||||||
|
labelOverride?: string;
|
||||||
|
itemsBefore?: [{
|
||||||
|
name: string;
|
||||||
|
unit: string;
|
||||||
|
amount: string;
|
||||||
|
}];
|
||||||
|
itemsAfter?: [{
|
||||||
|
name: string;
|
||||||
|
unit: string;
|
||||||
|
amount: string;
|
||||||
|
}];
|
||||||
|
// Populated after server-side resolution
|
||||||
|
resolvedRecipe?: {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
short_name: string;
|
||||||
|
ingredients?: IngredientSection[];
|
||||||
|
translations?: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IngredientItem = IngredientSection | IngredientReference;
|
||||||
|
|
||||||
|
// Instruction discriminated union types
|
||||||
|
export type InstructionSection = {
|
||||||
|
name?: string;
|
||||||
|
type: 'section';
|
||||||
|
steps: [string];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InstructionReference = {
|
||||||
|
name?: string;
|
||||||
|
type: 'reference';
|
||||||
|
baseRecipeRef: string; // ObjectId as string
|
||||||
|
includeInstructions: boolean;
|
||||||
|
showLabel: boolean;
|
||||||
|
labelOverride?: string;
|
||||||
|
stepsBefore?: [string];
|
||||||
|
stepsAfter?: [string];
|
||||||
|
// Populated after server-side resolution
|
||||||
|
resolvedRecipe?: {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
short_name: string;
|
||||||
|
instructions?: InstructionSection[];
|
||||||
|
translations?: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InstructionItem = InstructionSection | InstructionReference;
|
||||||
|
|
||||||
// Translated recipe type (English version)
|
// Translated recipe type (English version)
|
||||||
export type TranslatedRecipeType = {
|
export type TranslatedRecipeType = {
|
||||||
short_name: string;
|
short_name: string;
|
||||||
@@ -17,18 +85,8 @@ export type TranslatedRecipeType = {
|
|||||||
note?: string;
|
note?: string;
|
||||||
category: string;
|
category: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
ingredients?: [{
|
ingredients?: IngredientItem[];
|
||||||
name?: string;
|
instructions?: InstructionItem[];
|
||||||
list: [{
|
|
||||||
name: string;
|
|
||||||
unit: string;
|
|
||||||
amount: string;
|
|
||||||
}]
|
|
||||||
}];
|
|
||||||
instructions?: [{
|
|
||||||
name?: string;
|
|
||||||
steps: string[];
|
|
||||||
}];
|
|
||||||
images?: [{
|
images?: [{
|
||||||
alt: string;
|
alt: string;
|
||||||
caption?: string;
|
caption?: string;
|
||||||
@@ -68,20 +126,11 @@ export type RecipeModelType = {
|
|||||||
portions?: string;
|
portions?: string;
|
||||||
cooking?: string;
|
cooking?: string;
|
||||||
total_time?: string;
|
total_time?: string;
|
||||||
ingredients?: [{
|
ingredients?: IngredientItem[];
|
||||||
name?: string;
|
instructions?: InstructionItem[];
|
||||||
list: [{
|
|
||||||
name: string;
|
|
||||||
unit: string;
|
|
||||||
amount: string;
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
instructions?: [{
|
|
||||||
name?: string;
|
|
||||||
steps: [string]
|
|
||||||
}]
|
|
||||||
preamble?: String
|
preamble?: String
|
||||||
addendum?: string
|
addendum?: string
|
||||||
|
isBaseRecipe?: boolean;
|
||||||
translations?: {
|
translations?: {
|
||||||
en?: TranslatedRecipeType;
|
en?: TranslatedRecipeType;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -308,20 +308,50 @@ class DeepLTranslationService {
|
|||||||
// Add ingredient names and list items
|
// Add ingredient names and list items
|
||||||
const ingredients = recipe.ingredients || [];
|
const ingredients = recipe.ingredients || [];
|
||||||
ingredients.forEach((ing: any) => {
|
ingredients.forEach((ing: any) => {
|
||||||
textsToTranslate.push(ing.name || '');
|
// Handle base recipe references differently
|
||||||
(ing.list || []).forEach((item: any) => {
|
if (ing.type === 'reference') {
|
||||||
textsToTranslate.push(item.name || '');
|
// Only translate labelOverride if present
|
||||||
textsToTranslate.push(item.unit || ''); // Translate units (EL→tbsp, TL→tsp)
|
textsToTranslate.push(ing.labelOverride || '');
|
||||||
});
|
// Translate items before and after
|
||||||
|
(ing.itemsBefore || []).forEach((item: any) => {
|
||||||
|
textsToTranslate.push(item.name || '');
|
||||||
|
textsToTranslate.push(item.unit || '');
|
||||||
|
});
|
||||||
|
(ing.itemsAfter || []).forEach((item: any) => {
|
||||||
|
textsToTranslate.push(item.name || '');
|
||||||
|
textsToTranslate.push(item.unit || '');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Regular ingredient section
|
||||||
|
textsToTranslate.push(ing.name || '');
|
||||||
|
(ing.list || []).forEach((item: any) => {
|
||||||
|
textsToTranslate.push(item.name || '');
|
||||||
|
textsToTranslate.push(item.unit || ''); // Translate units (EL→tbsp, TL→tsp)
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add instruction names and steps
|
// Add instruction names and steps
|
||||||
const instructions = recipe.instructions || [];
|
const instructions = recipe.instructions || [];
|
||||||
instructions.forEach((inst: any) => {
|
instructions.forEach((inst: any) => {
|
||||||
textsToTranslate.push(inst.name || '');
|
// Handle base recipe references differently
|
||||||
(inst.steps || []).forEach((step: string) => {
|
if (inst.type === 'reference') {
|
||||||
textsToTranslate.push(step || '');
|
// Only translate labelOverride if present
|
||||||
});
|
textsToTranslate.push(inst.labelOverride || '');
|
||||||
|
// Translate steps before and after
|
||||||
|
(inst.stepsBefore || []).forEach((step: string) => {
|
||||||
|
textsToTranslate.push(step || '');
|
||||||
|
});
|
||||||
|
(inst.stepsAfter || []).forEach((step: string) => {
|
||||||
|
textsToTranslate.push(step || '');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Regular instruction section
|
||||||
|
textsToTranslate.push(inst.name || '');
|
||||||
|
(inst.steps || []).forEach((step: string) => {
|
||||||
|
textsToTranslate.push(step || '');
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add image alt and caption texts
|
// Add image alt and caption texts
|
||||||
@@ -358,18 +388,56 @@ class DeepLTranslationService {
|
|||||||
},
|
},
|
||||||
category: translatedCategory,
|
category: translatedCategory,
|
||||||
tags: tags.map(() => translated[index++]),
|
tags: tags.map(() => translated[index++]),
|
||||||
ingredients: ingredients.map((ing: any) => ({
|
ingredients: ingredients.map((ing: any) => {
|
||||||
name: translated[index++],
|
if (ing.type === 'reference') {
|
||||||
list: (ing.list || []).map((item: any) => ({
|
return {
|
||||||
name: translated[index++],
|
type: 'reference',
|
||||||
unit: translated[index++], // Use translated unit (tbsp, tsp, etc.)
|
name: ing.name,
|
||||||
amount: item.amount,
|
baseRecipeRef: ing.baseRecipeRef,
|
||||||
}))
|
includeIngredients: ing.includeIngredients,
|
||||||
})),
|
showLabel: ing.showLabel,
|
||||||
instructions: instructions.map((inst: any) => ({
|
labelOverride: translated[index++],
|
||||||
name: translated[index++],
|
itemsBefore: (ing.itemsBefore || []).map((item: any) => ({
|
||||||
steps: (inst.steps || []).map(() => translated[index++])
|
name: translated[index++],
|
||||||
})),
|
unit: translated[index++],
|
||||||
|
amount: item.amount,
|
||||||
|
})),
|
||||||
|
itemsAfter: (ing.itemsAfter || []).map((item: any) => ({
|
||||||
|
name: translated[index++],
|
||||||
|
unit: translated[index++],
|
||||||
|
amount: item.amount,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
name: translated[index++],
|
||||||
|
list: (ing.list || []).map((item: any) => ({
|
||||||
|
name: translated[index++],
|
||||||
|
unit: translated[index++],
|
||||||
|
amount: item.amount,
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
instructions: instructions.map((inst: any) => {
|
||||||
|
if (inst.type === 'reference') {
|
||||||
|
return {
|
||||||
|
type: 'reference',
|
||||||
|
name: inst.name,
|
||||||
|
baseRecipeRef: inst.baseRecipeRef,
|
||||||
|
includeInstructions: inst.includeInstructions,
|
||||||
|
showLabel: inst.showLabel,
|
||||||
|
labelOverride: translated[index++],
|
||||||
|
stepsBefore: (inst.stepsBefore || []).map(() => translated[index++]),
|
||||||
|
stepsAfter: (inst.stepsAfter || []).map(() => translated[index++]),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
name: translated[index++],
|
||||||
|
steps: (inst.steps || []).map(() => translated[index++])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
images: images.map((img: any) => ({
|
images: images.map((img: any) => ({
|
||||||
alt: translated[index++],
|
alt: translated[index++],
|
||||||
caption: translated[index++],
|
caption: translated[index++],
|
||||||
|
|||||||
Reference in New Issue
Block a user