all repos — momix @ a4589ea3d5ac14f43d7a7bfff7ffb8b71b170a88

A CLI tool to manage recipes for Thermomix

feat: ✨ Add web server
Tim Izzo tim@5ika.ch
Fri, 02 Jan 2026 20:01:51 +0100
commit

a4589ea3d5ac14f43d7a7bfff7ffb8b71b170a88

parent

623410e806f3318fb20af938664583ced886ce0e

A commands/grocery.ts

@@ -0,0 +1,24 @@

+import type { DatabaseSync } from "node:sqlite"; +import { Checkbox } from "@cliffy/prompt"; +import { Table } from "@cliffy/table"; +import { getGroceryList, getRecipesList } from "../services.ts"; + +export default async (db: DatabaseSync) => { + const recipes = getRecipesList(db); + + const choices = await Checkbox.prompt({ + message: "Select recipes", + options: recipes.map((recipe) => ({ + name: recipe.name as string, + value: recipe.id, + })), + }); + + const ingredients = getGroceryList(db, choices as number[]); + + const table = new Table( + ...ingredients.map((item) => [item.name, item.quantity, item.unit]) + ).header(["Name", "Quantity", "Unit"]); + + console.log(table.border().sort().toString()); +};
M commands/list.tscommands/list.ts

@@ -1,8 +1,9 @@

import { Table } from "@cliffy/table"; import type { DatabaseSync } from "node:sqlite"; +import { getRecipesList } from "../services.ts"; export default (db: DatabaseSync) => { - const recipes = db.prepare("SELECT * FROM recipes").all(); + const recipes = getRecipesList(db); const cells = recipes.map((recipe) => [ recipe.id as string, recipe.name as string,

@@ -10,6 +11,5 @@ recipe.url as string,

recipe.createdAt as string, ]); const table = new Table(["ID", "Name", "URL", "Created At"], ...cells); - console.log(table.border().toString()); };
D commands/select.ts

@@ -1,34 +0,0 @@

-import type { DatabaseSync } from "node:sqlite"; -import { Checkbox } from "@cliffy/prompt"; -import { Table } from "@cliffy/table"; - -export default async (db: DatabaseSync) => { - const recipes = db.prepare("SELECT * FROM recipes").all(); - - const choices = await Checkbox.prompt({ - message: "Select recipes", - options: recipes.map((recipe) => ({ - name: recipe.name as string, - value: recipe, - })), - }); - - const rawIngredients = choices.flatMap((recipe) => - JSON.parse(recipe.ingredients as string) - ); - - const ingredients = rawIngredients.reduce((acc, ingredient) => { - const existingIngredient = acc.find( - (i) => i.name === ingredient.name && i.unit === ingredient.unit - ); - if (existingIngredient) existingIngredient.value += ingredient.value; - else acc.push(ingredient); - return acc; - }, [] as { name: string; value: number; unit: string }[]); - - const table = new Table( - ...ingredients.map((item) => [item.name, item.value, item.unit]) - ).header(["Name", "Value", "Unit"]); - - console.log(table.border().sort().toString()); -};
M commands/store.tscommands/store.ts

@@ -1,48 +1,7 @@

import type { DatabaseSync } from "node:sqlite"; -import { callLLM } from "../utils/llm.ts"; -import { decodeHtmlChars } from "../utils/html.ts"; +import { storeRecipe } from "../services.ts"; export default async (db: DatabaseSync, recipeId: string) => { - let recipeUrl = ""; - if (!recipeId) throw new Error("Please provide a recipe ID"); - else if (recipeId.startsWith("https://")) recipeUrl = recipeId; - else recipeUrl = `https://cookidoo.fr/recipes/recipe/fr-FR/${recipeId}`; - - const recipePage = await fetch(recipeUrl).then((res) => res.text()); - const jsonLdTag = recipePage.match( - /<script type="application\/ld\+json">(.*?)<\/script>/s - )?.[1]; - if (!jsonLdTag) throw new Error("No JSON-LD tag found"); - - const recipeJson = JSON.parse(jsonLdTag); - - const existingItem = db - .prepare("SELECT * FROM recipes WHERE url = ?") - .get(recipeUrl); - if (existingItem) { - console.log("Recipe already exists in db"); - db.close(); - Deno.exit(0); - } - - const rawIngredientsJson = await callLLM(` - À partir de la liste suivante, renvoi un objet JSON avec unit, value, name pour chacun des éléments. - ${decodeHtmlChars(recipeJson.recipeIngredient.join("\n"))} - Si la quantité est un nombre décimal, convertis le. - Utilise de préférences les unités les plus courantes comme g, pincée, cuillère à café, gousse. - Les unités doivent toujours être au singulier. - `); - - const recipe = { - name: recipeJson.name, - ingredients: rawIngredientsJson, - imageUrl: recipeJson.image, - url: recipeUrl, - }; - - db.prepare( - `INSERT INTO recipes (url, name, ingredients, imageUrl) VALUES (?, ?, ?, ?)` - ).run(recipe.url, recipe.name, recipe.ingredients, recipe.imageUrl); - - console.log(`New recipe "${recipe.name}" added to db.`); + const result = await storeRecipe(db, recipeId); + if (!result) Deno.exit(0); };
M db.tsdb.ts

@@ -13,4 +13,11 @@ createdAt DATETIME DEFAULT CURRENT_TIMESTAMP

) `); +db.exec(` + CREATE TABLE IF NOT EXISTS groceryLists ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, + recipeIds TEXT NOT NULL + )`); + export default db;
M deno.jsondeno.json

@@ -1,11 +1,18 @@

{ "tasks": { - "cli": "deno run --allow-net --allow-read --allow-write --allow-env main.ts" + "cli": "deno run --allow-net --allow-read --allow-write --allow-env main.ts", + "web": "deno serve --watch --allow-net --allow-read --allow-write --allow-env server.tsx" }, "imports": { "@cliffy/command": "jsr:@cliffy/command@1.0.0-rc.8", "@cliffy/table": "jsr:@cliffy/table@1.0.0-rc.8", "@cliffy/prompt": "jsr:@cliffy/prompt@1.0.0-rc.8", - "@std/dotenv": "jsr:@std/dotenv@^0.225.6" + "@std/dotenv": "jsr:@std/dotenv@^0.225.6", + "hono": "npm:hono", + "hono/": "npm:/hono/" + }, + "compilerOptions": { + "jsx": "precompile", + "jsxImportSource": "hono/jsx" } }
M deno.lockdeno.lock

@@ -14,7 +14,8 @@ "jsr:@std/encoding@~1.0.5": "1.0.10",

"jsr:@std/fmt@~1.0.2": "1.0.8", "jsr:@std/io@~0.224.9": "0.224.9", "jsr:@std/path@~1.0.6": "1.0.9", - "jsr:@std/text@~1.0.7": "1.0.16" + "jsr:@std/text@~1.0.7": "1.0.16", + "npm:hono@*": "4.11.1" }, "jsr": { "@cliffy/ansi@1.0.0-rc.8": {

@@ -88,12 +89,18 @@ "@std/text@1.0.16": {

"integrity": "ddb9853b75119a2473857d691cf1ec02ad90793a2e8b4a4ac49d7354281a0cf8" } }, + "npm": { + "hono@4.11.1": { + "integrity": "sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg==" + } + }, "workspace": { "dependencies": [ "jsr:@cliffy/command@1.0.0-rc.8", "jsr:@cliffy/prompt@1.0.0-rc.8", "jsr:@cliffy/table@1.0.0-rc.8", - "jsr:@std/dotenv@~0.225.6" + "jsr:@std/dotenv@~0.225.6", + "npm:hono@*" ] } }
M main.tsmain.ts

@@ -3,7 +3,7 @@ import db from "./db.ts";

import store from "./commands/store.ts"; import list from "./commands/list.ts"; -import select from "./commands/select.ts"; +import select from "./commands/grocery.ts"; await new Command() .name("cookidoo")

@@ -19,7 +19,7 @@

.command("list", "Liste stored recipes") .action(() => list(db)) - .command("select", "Select recipes for course list") + .command("grocery", "Select recipes for grocery list") .action(() => select(db)) .parse(Deno.args);
D recipe.html

@@ -1,934 +0,0 @@

-<!DOCTYPE html> -<html - lang="fr-FR" - class="cicd2-theme"> -<head> - <meta property="og:url" content="https://cookidoo.fr/recipes/recipe/fr-FR/r829310"/> - <meta property="og:title" content="Marinade à la jamaïcaine pour le poulet"/> - <meta property="og:description" content="Un monde de recettes Thermomix® – Cookidoo® vous propose des plats délicieux dans le monde entier. -Avec des milliers de recettes et d’idées, vous trouverez une source d’inspiration succulente à chaque fois que vous vous connecterez."/> - <meta property="og:image" content="https://assets.tmecosys.com/image/upload/t_web_rdp_recipe_584x480/img/recipe/ras/Assets/51ae122040bc0b935382af31b7e3ce26/Derivates/c034c47d3681d4ae91896e76361bdf5205a738f0.jpg"/> - <meta name="robots" content="noarchive"/> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> - <link rel="stylesheet" href="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/pl-core-29.0.0-18aff5db89ac31d72351aa87f8d2dd94.css"> - <link rel="stylesheet" href="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/cicd2-theme-29.0.0-5eba483bfbb03f3a99d1d6c8cc61c91f.css"> - <link rel="stylesheet" href="https://recipepublic-all.prod.external.eu-tm-prod.vorwerk-digital.com/bundle-700b0b14b57bb2a7bf72133389c7f0a6.css"> - <link rel="icon" href="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/favicon-02a92602e0cf506ebd0186892a17fd82.ico"> - <link rel="preconnect" href="https://assets.tmecosys.com" crossorigin="anonymous"> - <script>"use strict";(()=>{function c(n){let t=document.cookie.match(new RegExp("(^| )"+n+"=([^;]+)"));if(t)return t[2]}var e={get:c};e.get("v-authenticated")?document.documentElement.classList.add("is-authenticated"):document.documentElement.classList.add("is-unauthenticated");})(); -</script> - <title>Marinade à la jamaïcaine pour le poulet - Cookidoo® – la plateforme de recettes officielle de Thermomix®</title> - <link rel="stylesheet" href="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/pl-recipe-2.18.1-4949e3c3f2b6c536234d63e48d89c215.css"> - <script type="application/ld+json">{"@context":"http://schema.org/","@type":"Recipe","name":"Marinade à la jamaïcaine pour le poulet","image":"https://assets.tmecosys.com/image/upload/t_web_rdp_recipe_584x480_1_5x/img/recipe/ras/Assets/51ae122040bc0b935382af31b7e3ce26/Derivates/c034c47d3681d4ae91896e76361bdf5205a738f0.jpg","totalTime":"PT10M","cookTime":"PT10M","prepTime":"PT10M","recipeYield":"350 g","recipeCategory":["Sauces, dips et tartinades","Plat principal - Viandes"],"recipeIngredient":["1 c. à soupe d'origan déshydraté","1 c. à café de graines de coriandre","100 g d'oignons","150 g de tomates","&frac12; c. à café de paprika","2 gousses d'ail","15 g gingembres frais","1 c. à café de thym déshydraté","1 - 2 piment frais","30 g d'huile d'olive","30 g de sauce de soja","15 g de jus de citron"],"nutrition":{"@type":"NutritionInformation","calories":"419.4 kcal","carbohydrateContent":"32.5 g","fatContent":"31.4 g","proteinContent":"7.6 g"},"inLanguage":"fr-FR","author":{"@type":"Organization","name":"Vorwerk International & Co. KmG","address":"Wolleraustrasse 11a\n8807 Freienbach\nSuisse","url":"https://cookidoo.fr"},"aggregateRating":{"@id":"AggregatedRating"}}</script> -</head> - -<body> - <core-user-info - condition="html.is-authenticated" - community-profile="/community/profile/fr-FR" - devices="/customer-devices/api/my-devices/versions" - > - </core-user-info> - - <div class="page-content"> - - <header tabindex="-1" class="page-header"> - <div class="page-header__content"> - <a href="#main-content" class="link--skip">Skip to main content</a> - <a class="logo page-header__home authenticated-only" href="/foundation/fr-FR/for-you" - aria-label="Link to the home page"> - <img class="logo" src="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/logo_thermomix-02469c2fb4fca55fc3c397286d9e7fe0.svg" - alt="Thermomix®"> - </a> - <a class="logo page-header__home unauthenticated-only" href="/foundation/fr-FR/explore" - aria-label="Link to the home page"> - <img class="logo" src="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/logo_thermomix-02469c2fb4fca55fc3c397286d9e7fe0.svg" - alt="Thermomix®"> - </a> - <core-nav class="page-header__nav"> - <nav class="core-nav__nav" role="navigation"> - <button class="core-nav__trigger">Menu</button> - <div class="core-nav__container"> - <ul class="core-nav__main-links authenticated-only"> - <li class="core-nav__item"> - <a href="/foundation/fr-FR/for-you" - class="core-nav__link">Pour Vous</a> - </li> - <li class="core-nav__item"> - <a href="/foundation/fr-FR/explore" - class="core-nav__link">Découvrir</a> - </li> - <li class="core-nav__item"> - <a href="/organize/fr-FR/my-recipes" - class="core-nav__link">Mes recettes</a> - </li> - <li class="core-nav__item"> - <a href="/planning/fr-FR/my-week" - class="core-nav__link">Ma semaine</a> - </li> - <li class="core-nav__item"> - <a href="/shopping/fr-FR" - class="core-nav__link">Liste de courses</a> - </li> - </ul> - <ul class=" core-nav__main-links unauthenticated-only"> - <li class="core-nav__item"> - <a href="/foundation/fr-FR/explore" - class="core-nav__link">Découvrir</a> - </li> - <li class="core-nav__item"> - <a href="/foundation/fr-FR/membership" - class="core-nav__link">Abonnement</a> - </li> - <li class="core-nav__item"> - <a href="/foundation/fr-FR/help" - class="core-nav__link">Aide</a> - </li> - </ul> - <ul class="core-nav__links unauthenticated-only"> - <li class="core-nav__item"> - <a href="/ciam/register/start" - class="core-nav__link page-header__sign-up page-header__icon">Créer un compte</a> - </li> - <li class="core-nav__item"> - <a href="/profile/fr-FR/login?redirectAfterLogin=%2Frecipes%2Frecipe%2Ffr-FR%2Fr829310" - class="core-nav__link page-header__login page-header__icon">Se connecter</a> - </li> - </ul> - <div role="separator" aria-orientation="vertical" - class="core-nav__separator separator-vertical separator-vertical--silver-20"></div> - <core-user-profile class="authenticated-only"> - <core-dropdown-menu class="core-nav__dropdown core-nav__dropdown--profile" align="bottom-right"> - <button class="core-dropdown-menu__trigger core-nav__dropdown-trigger"> - <span class="core-nav__dropdown-trigger-icon" aria-hidden="true"></span> - <img class="core-nav__dropdown-trigger-picture" src alt> - <span class="core-dropdown-menu__trigger-text"> - Profil - </span> - </button> - <div class="core-dropdown-menu__content core-nav__dropdown-content"> - <ul class="core-dropdown-list core-nav__dropdown-list"> - <li class="core-community-profile__link"> - <a href="/community/profile/fr-FR" - class="core-dropdown-list__item core-nav__link core-nav__link--community"> - <core-community-profile> - <span class="core-community-profile__icon" aria-hidden="true"></span> - <img class="core-community-profile__picture" src alt> - <div class="core-community-profile__heading-group"> - <span class="core-community-profile__header">Profil</span> - <span class="core-community-profile__subheader">Voir le profil</span> - </div> - </core-community-profile> - </a> - </li> - <li> - <a href="/commerce/fr-FR/membership" - class="core-dropdown-list__item core-nav__link">Mon compte</a> - </li> - <li> - <a href="/foundation/fr-FR/help" - class="core-dropdown-list__item core-nav__link">Aide</a> - </li> - <li> - <a href="/profile/logout" - class="core-dropdown-list__item core-nav__link">Se déconnecter</a> - </li> - </ul> - </div> - </core-dropdown-menu> - </core-user-profile> - </div> - <div role="separator" aria-orientation="vertical" - class="core-nav__separator separator-vertical separator-vertical--silver-20"></div> - </nav> - </core-nav> - <a class="page-header__search page-header__icon" href="/search/fr-FR" - aria-label="Recherche">Recherche</a> - </div> - </header> - <recipe-scrollspy> - <nav class="recipe-scrollspy__nav"> - <a href="#ingredients-section" class="recipe-scrollspy__link">Ingrédients</a> - - <a href="#difficulty-section" class="recipe-scrollspy__link">Niveau</a> - - <a href="#nutrition-section" class="recipe-scrollspy__link">Informations nutritionnelles</a> - - <a href="#also-featured-in-section" class="recipe-scrollspy__link">Également inclus dans</a> - - <a id="recipe-scrollspy-alternative-recipes" href="#alternative-recipes" class="recipe-scrollspy__link">Vous pourriez aussi aimer...</a> - </nav> - </recipe-scrollspy> - - <recipe-details id="main-content"> - <recipe-card> - <div class="recipe-card__wrapper"> - <div class="recipe-card__image-wrapper"> - <core-image-loader> - <img - class="recipe-card__image" - src="https://assets.tmecosys.com/image/upload/t_web_rdp_recipe_584x480/img/recipe/ras/Assets/51ae122040bc0b935382af31b7e3ce26/Derivates/c034c47d3681d4ae91896e76361bdf5205a738f0.jpg" - srcset="https://assets.tmecosys.com/image/upload/t_web_rdp_recipe_584x480/img/recipe/ras/Assets/51ae122040bc0b935382af31b7e3ce26/Derivates/c034c47d3681d4ae91896e76361bdf5205a738f0.jpg 584w, https://assets.tmecosys.com/image/upload/t_web_rdp_recipe_584x480_1_5x/img/recipe/ras/Assets/51ae122040bc0b935382af31b7e3ce26/Derivates/c034c47d3681d4ae91896e76361bdf5205a738f0.jpg 876w" - sizes="(min-width: 1333px) 584px, (min-width: 768px) 50vw, 100vw" - alt="Marinade à la jamaïcaine pour le poulet" - title="Marinade à la jamaïcaine pour le poulet"/> - </core-image-loader> - </div> - <div class="recipe-card__info"> - - <div class="recipe-card__header"> - <div class="recipe-card__header-left"> - <rdp-badges id="tm-versions-modal"> - <button class="core-chip-button core-chip-button--flat core-chip-button--x-small"> - TM7 - </button> - <button class="core-chip-button core-chip-button--flat core-chip-button--x-small"> - TM6 - </button> - </rdp-badges> - - <core-modal - trigger-id="tm-versions-modal" - class="tm-versions-modal" - prevent-body-scroll="true" - hidden> - <div class="core-modal__wrapper"> - <div class="core-modal__container" role="dialog" aria-modal="true"> - - <div class="core-modal__header"> - <h2>Appareils et accessoires</h2> - <button class="core-modal__close" aria-label="Close Modal"></button> - </div> - - <core-scrollbar class="core-modal__content" fadeout-top> - <div class="core-scrollbar__content"> - - <rdp-tm-versions> - <p class="rdp-tm-versions__description">Cette recette est conçue pour une combinaison spécifique d'appareils et d'accessoires. Sans cette configuration requise, nous ne pouvons pas garantir un résultat satisfaisant.</p> - <div class="rdp-tm-versions__list"> - <div class="rdp-tm-versions__item"> - <img src="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/tm7-83b22c91a1a1e7fee3797168f05f9754.png" class="rdp-tm-versions__image"/> - <div class="rdp-tm-versions__wrapper"> - <span class="rdp-tm-versions__name">Thermomix® TM7</span> - <span class="rdp-tm-versions__compatibility"> - <span class="icon icon--checkmark-circle icon--xxxs"></span>Compatible - </span> - </div> - </div> - <div class="rdp-tm-versions__item"> - <img src="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/tm6-fff867f1cfc7f35118b8b6dfffca8339.png" class="rdp-tm-versions__image"/> - <div class="rdp-tm-versions__wrapper"> - <span class="rdp-tm-versions__name">Thermomix® TM6</span> - <span class="rdp-tm-versions__compatibility"> - <span class="icon icon--checkmark-circle icon--xxxs"></span>Compatible - </span> - </div> - </div> - </div> - </rdp-tm-versions> - - - </div> - </core-scrollbar> - - <div class="core-modal__footer"> - <a class="button--inline rdp-tm-versions__more" href="/foundation/fr-FR/thermomix-compatibility">Plus d’informations</a> - </div> - </div> - </div> - </core-modal> - - </div> - </div> - - <div class="recipe-card__content"> - <core-ellipsis lines-count="3"> - <h1 class="recipe-card__section recipe-card__name">Marinade à la jamaïcaine pour le poulet</h1> - </core-ellipsis> - - <core-rating> - <div class="core-rating__rating-list"> - <span class="core-rating__point core-rating__point--full"></span> - <span class="core-rating__point core-rating__point--full"></span> - <span class="core-rating__point core-rating__point--full"></span> - <span class="core-rating__point core-rating__point--full"></span> - <span class="core-rating__point core-rating__point--half"></span> - </div> - <span class="core-rating__counter">4.3</span> - <span class="core-rating__label"> - - 42 évaluation - - - </span> - <script type="application/ld+json"> - { - "@context": "http://schema.org", - "@type": "AggregateRating", - "@id": "AggregatedRating", - "ratingValue": 4.3, - "reviewCount": 42 - } - </script> -</core-rating> - - <div class="recipe-card__cook-params"> - <div class="recipe-card__cook-param"> - <span class="icon icon--time-preparation"></span> - <span>Prép. 10min </span> - </div> - <div class="recipe-card__cook-param"> - <span class="icon icon--time"></span> - <span>Total 10min</span> - </div> - <div class="recipe-card__cook-param"> - <span class="icon icon--servings"></span> - <span>350 g</span> - </div> - </div> - </div> - - <div class="recipe-card__footer"> - <a class="button--primary recipe-card__action-button recipe-card__action-button--primary" - title="Créer un compte gratuitement" - href="/ciam/register/start">Créer un compte gratuitement</a> - </div> - </div> - </div> - </recipe-card> - - <recipe-content> - <div class="recipe-content__left"> - <div mobile-order="1"> - <div id="ingredients-section" class="recipe-content__section"> - <h4 class="recipe-content__title">Ingrédients</h4> - <div class="recipe-content__inner-section"> - <ul class="ul--clean"> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/1140" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/1140 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/1140 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/1140 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 1 - c. à soupe - d&#x27;origan déshydraté - </span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/299" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/299 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/299 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/299 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 1 - c. à café - de graines de coriandre - </span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/18" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/18 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/18 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/18 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 100 - g - d&#x27;oignons - </span> - <span class="recipe-ingredient__description">coupés en deux</span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/30" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/30 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/30 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/30 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 150 - g - de tomates - </span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/2765" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/2765 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/2765 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/2765 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - &frac12; - c. à café - de paprika - </span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/352" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/352 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/352 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/352 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 2 - - gousses d&#x27;ail - </span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/11" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/11 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/11 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/11 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 15 - g - gingembres frais - </span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/986" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/986 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/986 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/986 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 1 - c. à café - de thym déshydraté - </span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/62" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/62 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/62 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/62 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 1 - 2 - - piment frais - </span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/195" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/195 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/195 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/195 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 30 - g - d&#x27;huile d&#x27;olive - </span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/159" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/159 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/159 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/159 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 30 - g - de sauce de soja - </span> - </div> - </div> - </recipe-ingredient> - </li> - <li> - <recipe-ingredient> - <div class="recipe-ingredient__wrapper"> - <img class="recipe-ingredient__image" src="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/46" - srcset="https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48/icons/ingredient_icons/46 48w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_1_5x/icons/ingredient_icons/46 72w, https://assets.tmecosys.com/image/upload/t_web_ingredient_48x48_2x/icons/ingredient_icons/46 96w" - sizes="48px" - /> - - <div class="recipe-ingredient__content"> - <span class="recipe-ingredient__name"> - 15 - g - de jus de citron - </span> - </div> - </div> - </recipe-ingredient> - </li> - </ul> - </div> - </div> - <hr> - </div> - <div mobile-order="3"> - <div id="difficulty-section" class="recipe-content__section"> - <h4 class="recipe-content__title">Niveau</h4> - <rdp-difficulty> - <span class="icon icon--s icon--chef-hat"></span> - <p>facile</p> - </rdp-difficulty> - </div> - <hr> - <div id="nutrition-section" class="recipe-content__section"> - <h4 class="recipe-content__title"> - Infos nut. - <span class="recipe-content__subtitle">par 350 g</span> - </h4> - <rdp-nutritious> - <div class="rdp-nutritious__item"> - <span class="rdp-nutritious__name">Sodium</span> - <span class="rdp-nutritious__value"> - 1671.4 mg - </span> - </div> - <div class="rdp-nutritious__item"> - <span class="rdp-nutritious__name">Protides</span> - <span class="rdp-nutritious__value"> - 7.6 g - </span> - </div> - <div class="rdp-nutritious__item"> - <span class="rdp-nutritious__name">Calories</span> - <span class="rdp-nutritious__value"> - 1754.9 kJ / - 419.4 kcal - </span> - </div> - <div class="rdp-nutritious__item"> - <span class="rdp-nutritious__name">Lipides</span> - <span class="rdp-nutritious__value"> - 31.4 g - </span> - </div> - <div class="rdp-nutritious__item"> - <span class="rdp-nutritious__name">Fibre</span> - <span class="rdp-nutritious__value"> - 8 g - </span> - </div> - <div class="rdp-nutritious__item"> - <span class="rdp-nutritious__name">Graisses saturées</span> - <span class="rdp-nutritious__value"> - 4.3 g - </span> - </div> - <div class="rdp-nutritious__item"> - <span class="rdp-nutritious__name">Glucides</span> - <span class="rdp-nutritious__value"> - 32.5 g - </span> - </div> - </rdp-nutritious> - </div> - <hr> - </div> - </div> - <div class="recipe-content__right"> - <div mobile-order="2"> - <div class="recipe-content__section"> - <recipe-membership-banner> - <img src="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/cookidoo-world-da330b8ec91ef8ac5df385f0e440dffb.svg" class="recipe-membership-banner__image" /> - <h1 class="recipe-membership-banner__title">Vous aimez ce que vous voyez ?</h1> - <h4 class="recipe-membership-banner__subtitle">Cette recette et plus de 100 000 autres vous attendent !</h4> - <p class="recipe-membership-banner__description">Inscrivez-vous à notre essai gratuit de 30 jours et découvrez le monde de Cookidoo®. Sans engagement.</p> - <a href="/ciam/register/start" class="button--primary">Créer un compte gratuitement</a> - <a href="/foundation/fr-FR/membership" class="button--inline">Plus d’informations</a> - </recipe-membership-banner> - </div> - <hr> - </div> - <div mobile-order="4"> - <div id="also-featured-in-section" class="recipe-content__section"> - <h4 class="recipe-content__title">Également présenté dans</h4> - <rdp-collections> - <rdp-collection-tile> - <a class="rdp-collection-tile__wrapper" href="/collection/fr-FR/p/col460825"> - <img src="https://assets.tmecosys.com/image/upload/t_web_col_80x80/img/collection/ras/Assets/72a08da2e408dafd03e15df1953b8261/Derivates/ac5c5aec51ad95509a8772ef714942267e1eb53e.jpg" - srcset="https://assets.tmecosys.com/image/upload/t_web_col_80x80/img/collection/ras/Assets/72a08da2e408dafd03e15df1953b8261/Derivates/ac5c5aec51ad95509a8772ef714942267e1eb53e.jpg 80w, https://assets.tmecosys.com/image/upload/t_web_col_80x80_1_5x/img/collection/ras/Assets/72a08da2e408dafd03e15df1953b8261/Derivates/ac5c5aec51ad95509a8772ef714942267e1eb53e.jpg 120w, https://assets.tmecosys.com/image/upload/t_web_col_80x80_2x/img/collection/ras/Assets/72a08da2e408dafd03e15df1953b8261/Derivates/ac5c5aec51ad95509a8772ef714942267e1eb53e.jpg 160w" - sizes="80px" - class="rdp-collection-tile__image"> - <div class="rdp-collection-tile__content"> - <span class="rdp-collection-tile__name">Barbecue party</span> - <span class="rdp-collection-tile__info">7 Recettes<br>France</span> - </div> - </a> - </rdp-collection-tile> - </rdp-collections> - </div> - <hr> - </div> - </div> - </recipe-content> - </recipe-details> - - - - - <div id="alternative-recipes" class="l-content l-content--additional recipe-alternative-recipes"> - <core-stripe class="core-stripe--modern" aria-labelledby="stripe-header" aria-describedby="stripe-description" role="region" - data-category="VrkNavCategory-RPF-018"> - <h3 class="core-stripe__header" id="stripe-header"> - Vous pourriez aussi aimer... - </h3> - <div class="core-stripe__content"> - - - - <core-tile class="core-tile--expanded" id="r756329" data-recipe-id="r756329"><a class="link--alt" href="/recipes/recipe/fr-FR/r756329"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Poulet à la jamaïcaine" - title="Poulet à la jamaïcaine" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/6b1d4157-9803-48f0-97c7-b6000dd91673/Derivates/c1b5d931-ec23-4a83-8298-7e64a8e2b778" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/6b1d4157-9803-48f0-97c7-b6000dd91673/Derivates/c1b5d931-ec23-4a83-8298-7e64a8e2b778 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/6b1d4157-9803-48f0-97c7-b6000dd91673/Derivates/c1b5d931-ec23-4a83-8298-7e64a8e2b778 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/6b1d4157-9803-48f0-97c7-b6000dd91673/Derivates/c1b5d931-ec23-4a83-8298-7e64a8e2b778 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Poulet à la jamaïcaine</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">4.7</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(108)</span></core-rating><p class="core-tile__description-subline">1h 40min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r756329" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r756329" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r756329" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r756329" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r756329" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr756329" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r908668" data-recipe-id="r908668"><a class="link--alt" href="/recipes/recipe/fr-FR/r908668"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Poulet frit coréen" - title="Poulet frit coréen" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/810cab8c8e2db7d6f71b4a6681a8fa99/Derivates/b37cbe66dd81315b474ae2f36454c02fb1133cba" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/810cab8c8e2db7d6f71b4a6681a8fa99/Derivates/b37cbe66dd81315b474ae2f36454c02fb1133cba 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/810cab8c8e2db7d6f71b4a6681a8fa99/Derivates/b37cbe66dd81315b474ae2f36454c02fb1133cba 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/810cab8c8e2db7d6f71b4a6681a8fa99/Derivates/b37cbe66dd81315b474ae2f36454c02fb1133cba 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Poulet frit coréen</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">4.9</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(75)</span></core-rating><p class="core-tile__description-subline">3h 10min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r908668" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r908668" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r908668" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r908668" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r908668" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr908668" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r907216" data-recipe-id="r907216"><a class="link--alt" href="/recipes/recipe/fr-FR/r907216"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Pastilla au poulet, aux amandes et à la cannelle" - title="Pastilla au poulet, aux amandes et à la cannelle" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/23c0d981598924410a9d979961e4650a/Derivates/4ccf128453e573890fd08f43531b2c58b5b59c62" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/23c0d981598924410a9d979961e4650a/Derivates/4ccf128453e573890fd08f43531b2c58b5b59c62 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/23c0d981598924410a9d979961e4650a/Derivates/4ccf128453e573890fd08f43531b2c58b5b59c62 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/23c0d981598924410a9d979961e4650a/Derivates/4ccf128453e573890fd08f43531b2c58b5b59c62 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Pastilla au poulet, aux amandes et à la cannelle</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">3.9</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(17)</span></core-rating><p class="core-tile__description-subline">2h 5min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r907216" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r907216" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r907216" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r907216" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r907216" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr907216" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r903087" data-recipe-id="r903087"><a class="link--alt" href="/recipes/recipe/fr-FR/r903087"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Galettes de poisson thaïes" - title="Galettes de poisson thaïes" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/42f9ec10cbe31b2e8a9f8f2de8c160c2/Derivates/1312f74d6c7c2ba3ecd6d50e264b7b1c2d968449" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/42f9ec10cbe31b2e8a9f8f2de8c160c2/Derivates/1312f74d6c7c2ba3ecd6d50e264b7b1c2d968449 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/42f9ec10cbe31b2e8a9f8f2de8c160c2/Derivates/1312f74d6c7c2ba3ecd6d50e264b7b1c2d968449 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/42f9ec10cbe31b2e8a9f8f2de8c160c2/Derivates/1312f74d6c7c2ba3ecd6d50e264b7b1c2d968449 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Galettes de poisson thaïes</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">3.3</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(7)</span></core-rating><p class="core-tile__description-subline">30min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r903087" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r903087" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r903087" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r903087" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r903087" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr903087" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r901347" data-recipe-id="r901347"><a class="link--alt" href="/recipes/recipe/fr-FR/r901347"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Salade de spaghetti de concombre coréen" - title="Salade de spaghetti de concombre coréen" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/f98f688fa551d6d8b43e746e29614d3f/Derivates/661cb4e1bc2b9fe228947a0134732878502b8ba5" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/f98f688fa551d6d8b43e746e29614d3f/Derivates/661cb4e1bc2b9fe228947a0134732878502b8ba5 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/f98f688fa551d6d8b43e746e29614d3f/Derivates/661cb4e1bc2b9fe228947a0134732878502b8ba5 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/f98f688fa551d6d8b43e746e29614d3f/Derivates/661cb4e1bc2b9fe228947a0134732878502b8ba5 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Salade de spaghetti de concombre coréen</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">4.7</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(9)</span></core-rating><p class="core-tile__description-subline">25min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r901347" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r901347" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r901347" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r901347" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r901347" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr901347" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r828641" data-recipe-id="r828641"><a class="link--alt" href="/recipes/recipe/fr-FR/r828641"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Pad thaï au poulet" - title="Pad thaï au poulet" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/6a47c937e175adcf36f9fef947e800cc/Derivates/69dca562372f3db8e3eaace29bbd82f92de6abce" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/6a47c937e175adcf36f9fef947e800cc/Derivates/69dca562372f3db8e3eaace29bbd82f92de6abce 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/6a47c937e175adcf36f9fef947e800cc/Derivates/69dca562372f3db8e3eaace29bbd82f92de6abce 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/6a47c937e175adcf36f9fef947e800cc/Derivates/69dca562372f3db8e3eaace29bbd82f92de6abce 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Pad thaï au poulet</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">3.6</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(122)</span></core-rating><p class="core-tile__description-subline">2h</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r828641" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r828641" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r828641" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r828641" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r828641" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr828641" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r828628" data-recipe-id="r828628"><a class="link--alt" href="/recipes/recipe/fr-FR/r828628"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Ailes de poulet, sauce aux cacahuètes et riz basmati" - title="Ailes de poulet, sauce aux cacahuètes et riz basmati" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/97df9afaf5d541030f966f3cb7d25ac2/Derivates/098b33c682e55e4b348c6bea8aef2d45e7ffa06a" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/97df9afaf5d541030f966f3cb7d25ac2/Derivates/098b33c682e55e4b348c6bea8aef2d45e7ffa06a 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/97df9afaf5d541030f966f3cb7d25ac2/Derivates/098b33c682e55e4b348c6bea8aef2d45e7ffa06a 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/97df9afaf5d541030f966f3cb7d25ac2/Derivates/098b33c682e55e4b348c6bea8aef2d45e7ffa06a 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Ailes de poulet, sauce aux cacahuètes et riz basmati</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">3.6</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(24)</span></core-rating><p class="core-tile__description-subline">50min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r828628" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r828628" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r828628" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r828628" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r828628" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr828628" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r827021" data-recipe-id="r827021"><a class="link--alt" href="/recipes/recipe/fr-FR/r827021"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Achards de légumes" - title="Achards de légumes" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/0008488e3118ecf745aa5ff579258323/Derivates/6f45dd4c422e2e2006d7c3ac84e93f587c015872" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/0008488e3118ecf745aa5ff579258323/Derivates/6f45dd4c422e2e2006d7c3ac84e93f587c015872 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/0008488e3118ecf745aa5ff579258323/Derivates/6f45dd4c422e2e2006d7c3ac84e93f587c015872 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/0008488e3118ecf745aa5ff579258323/Derivates/6f45dd4c422e2e2006d7c3ac84e93f587c015872 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Achards de légumes</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">4.9</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(8)</span></core-rating><p class="core-tile__description-subline">40min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r827021" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r827021" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r827021" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r827021" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r827021" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr827021" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r818197" data-recipe-id="r818197"><a class="link--alt" href="/recipes/recipe/fr-FR/r818197"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Curry thaï de bœuf, pois gourmands et riz" - title="Curry thaï de bœuf, pois gourmands et riz" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/0de33bcf9fd7d19951a2084076babb50/Derivates/a987ef060d111f048e3749eec292734bc49cdd83" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/0de33bcf9fd7d19951a2084076babb50/Derivates/a987ef060d111f048e3749eec292734bc49cdd83 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/0de33bcf9fd7d19951a2084076babb50/Derivates/a987ef060d111f048e3749eec292734bc49cdd83 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/0de33bcf9fd7d19951a2084076babb50/Derivates/a987ef060d111f048e3749eec292734bc49cdd83 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Curry thaï de bœuf, pois gourmands et riz</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">3.8</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(39)</span></core-rating><p class="core-tile__description-subline">1h 15min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r818197" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r818197" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r818197" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r818197" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r818197" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr818197" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r908324" data-recipe-id="r908324"><a class="link--alt" href="/recipes/recipe/fr-FR/r908324"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Poitrine de porc croustillante et salade de riz chaud" - title="Poitrine de porc croustillante et salade de riz chaud" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/158a8c8584e3e8a67f438d46814baf2a/Derivates/eaf5ab6a53e456a9fc1cae959960d89bf233fac7" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/158a8c8584e3e8a67f438d46814baf2a/Derivates/eaf5ab6a53e456a9fc1cae959960d89bf233fac7 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/158a8c8584e3e8a67f438d46814baf2a/Derivates/eaf5ab6a53e456a9fc1cae959960d89bf233fac7 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/158a8c8584e3e8a67f438d46814baf2a/Derivates/eaf5ab6a53e456a9fc1cae959960d89bf233fac7 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Poitrine de porc croustillante et salade de riz chaud</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">4.7</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(10)</span></core-rating><p class="core-tile__description-subline">1h 30min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r908324" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r908324" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r908324" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r908324" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r908324" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr908324" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r828643" data-recipe-id="r828643"><a class="link--alt" href="/recipes/recipe/fr-FR/r828643"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Brochettes de poulet sauce satay et salade de concombres" - title="Brochettes de poulet sauce satay et salade de concombres" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/dd29ed1de21b1e86738986187fae87e6/Derivates/7ca357836bc9d6681ae3db44ec8d757e4b7f531c" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/dd29ed1de21b1e86738986187fae87e6/Derivates/7ca357836bc9d6681ae3db44ec8d757e4b7f531c 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/dd29ed1de21b1e86738986187fae87e6/Derivates/7ca357836bc9d6681ae3db44ec8d757e4b7f531c 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/dd29ed1de21b1e86738986187fae87e6/Derivates/7ca357836bc9d6681ae3db44ec8d757e4b7f531c 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Brochettes de poulet sauce satay et salade de concombres</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">4.5</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(22)</span></core-rating><p class="core-tile__description-subline">1h 35min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r828643" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r828643" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r828643" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r828643" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r828643" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr828643" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile><core-tile class="core-tile--expanded" id="r907173" data-recipe-id="r907173"><a class="link--alt" href="/recipes/recipe/fr-FR/r907173"><div class="core-tile__image-wrapper"> - <img - class="core-tile__image" - alt="Bricks de pommes de terre au thon et à la coriandre" - title="Bricks de pommes de terre au thon et à la coriandre" - src="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/1ce511d5f3b9a911d5c71366e152d339/Derivates/a8a3414bfa512da5a4fa11a4b1af0bb65f040a82" - sizes="221px" - decoding="async" - srcset="https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240/img/recipe/ras/Assets/1ce511d5f3b9a911d5c71366e152d339/Derivates/a8a3414bfa512da5a4fa11a4b1af0bb65f040a82 221w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_1_5x/img/recipe/ras/Assets/1ce511d5f3b9a911d5c71366e152d339/Derivates/a8a3414bfa512da5a4fa11a4b1af0bb65f040a82 331w, https://assets.tmecosys.com/image/upload/t_web_shared_recipe_221x240_2x/img/recipe/ras/Assets/1ce511d5f3b9a911d5c71366e152d339/Derivates/a8a3414bfa512da5a4fa11a4b1af0bb65f040a82 442w" - /> - </div><div class="core-tile__description-wrapper"><div class="core-tile__description"><core-ellipsis><p class="core-tile__description-text">Bricks de pommes de terre au thon et à la coriandre</p></core-ellipsis><button class="core-tile__trigger authenticated-only context-menu-trigger" aria-label="open context menu" type="button"></button></div><core-rating class="core-rating--short core-rating--small"><span class="core-rating__counter">4.6</span><span class="core-rating__point core-rating__point--full"></span><span class="core-rating__label">(71)</span></core-rating><p class="core-tile__description-subline">1h 10min</p></div></a><core-context-menu trigger-class="context-menu-trigger" class="translate-x-[0.5px]"><ul class="core-dropdown-list"><li><core-transclude href="/planning/fr-FR/transclude/manage-cook-today/r907173" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-bookmark/r907173" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/organize/fr-FR/transclude/manage-custom-list/r907173" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/planning/fr-FR/transclude/manage-add-to-myweek/r907173" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/shopping/fr-FR/partial/add-to-shopping-list/r907173" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li><core-transclude href="/created-recipes/fr-FR/partials/add-to-customer-recipes?recipeUrl=https%3A%2F%2Fcookidoo.fr%2Frecipes%2Frecipe%2Ffr-FR%2Fr907173" prevent-page-reload="true" on="context-menu-open" context="core-context-menu"></core-transclude></li><li data-error="401" data-redirect-param="redirectAfterLogin" class="display-none"><a href="/profile/fr-FR/login?redirectAfterLogin=%2Fsearch%2Ffr-FR%2Ffragments%2Fstripe%3Flimit%3D12%26lazyLoading%3Dtrue%26accessories%3DincludingFriend%252CincludingBladeCoverWithPeeler%252CincludingCutter%252CincludingSensor%26includeRating%3Dtrue%26like%3Dr829310" class="core-dropdown-list__item"><span class="icon" aria-hidden="true">refresh</span>Actualiser la connexion</a></li></ul></core-context-menu></core-tile> - - - - </div> - - - </core-stripe> - </div> - <core-toast aria-live="assertive"></core-toast> - - - -<core-footer lang="fr-FR"> - <footer class="core-footer__content"> - <div class="footer-copyright"> - <span class="core-footer__copyright">&#xA9; Copyright 2026</span> - </div> - <nav> - <ul class="core-footer__links"> - <li class="authenticated-only"> - <a class="core-footer__link link--alt" href="/consent/web/customers/fr-FR/documents/TOS"> - Conditions d&#x27;utilisation - </a> - </li> - <li class="authenticated-only"> - <a class="core-footer__link link--alt" href="/consent/web/customers/fr-FR/documents/PRIVACY"> - Politique de confidentialité - </a> - </li> - <li class="unauthenticated-only"> - <a class="core-footer__link link--alt" href="/consent/web/documents/fr-FR/latest/tos"> - Conditions d&#x27;utilisation - </a> - </li> - <li class="unauthenticated-only"> - <a class="core-footer__link link--alt" href="/consent/web/documents/fr-FR/latest/privacy"> - Politique de confidentialité - </a> - </li> - <li> - <a class="core-footer__link link--alt" href="/foundation/fr-FR/disclaimer">Non-responsabilité</a> - </li> - <li> - <a class="core-footer__link link--alt" href="/foundation/fr-FR/imprint">Mentions légales</a> - </li> - <li> - <a class="core-footer__link link--alt" href="/foundation/fr-FR/cookie-policy">Cookies</a> - </li> - <li> - <wf-fetch-modal - href="/foundation/fr-FR/partials/footer-modal-report-content?page=foundation/dsa" - selector="wf-report-content-modal" - > - <a class="core-footer__link link--alt" href="javascript:void(0)"> - Contenu du rapport - </a> - </wf-fetch-modal> - </li> - <li> - <a class="core-footer__link link--alt" href="/foundation/fr-FR/european-accessibility-act"> - Déclaration d&#x27;accessibilité - </a> - </li> - </ul> - </nav> - <core-fetch-modal - href="/foundation/fr-FR/partials/footer-modal?page=%2Frecipes%2Frecipe%2F%7Blang%7D%2Fr829310"> - <button class="core-footer__language-btn" aria-label="change language"> - <span class="icon" aria-hidden="true">language</span> - <span class="core-footer__current-lang">français</span> - <core-loader class="core-loader--dots"></core-loader> - </button> - </core-fetch-modal> - </footer> -</core-footer> - - <script - src="https://cdn.cookielaw.org/scripttemplates/otSDKStub.js" - type="text/javascript" - charset="UTF-8" - data-domain-script="9ccaf04f-2bce-4a4f-a618-ad33eaa5c8fd" - data-document-language="true"> - </script> - <script> - function OptanonWrapper() { - window.dispatchEvent(new CustomEvent('consentChange', { detail: { onetrustActiveGroups: window.OnetrustActiveGroups } })) - } - </script> -<script src="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/pl-web-foundation-footer-3.58.2-e4e3551269686cc40b4ef8bf6dcfa73d.js" crossorigin="anonymous"></script> -<link rel="stylesheet" href="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/pl-web-foundation-footer-3.58.2-7eeea7600f85cb74e2c24554e4440bb5.css" /> -<core-tos-privacy-update - update-url="/consent/web/customers/fr-FR/consent-update-flow" - button-text="Accepter" - default-headline="Des modifications ont été apportées à notre Politique de confidentialité ou à nos Conditions d&#x27;utilisation." - autoload-condition="html.is-authenticated" -></core-tos-privacy-update> -<core-feedback - url-api="/commerce/api/subscriptions/churn-feedback" - url-modal="/commerce/fr-FR/subscriptions/churn-feedback" - url-api-skip="/commerce/api/subscriptions/churn-feedback/skip" - message-success="Votre commentaire a été envoyé. Merci beaucoup !" - message-error="Une erreur s’est produite, votre commentaire n’a pas pu être envoyé. Veuillez réessayer." - call-on-init="true"> -</core-feedback> - <!-- Snowplow starts plowing --> - <meta name="xRequestMarket" content="fr"> - <meta name="marketCode" content="fr"> - <meta name="snowplowConnector" content="https://c.cookidoo.fr"> - <meta name="snowplowAppId" content="cookidoo"> - - <script type="text/javascript"> - window.addEventListener("consentChange", function(e) { - const oneTrustGroups = window.OnetrustActiveGroups - const userGivesConsent = oneTrustGroups.includes('C0002') - if (!userGivesConsent) { - if (!window.snowplow) return - window.snowplow('disableButtonClickTracking'); - window.snowplow('disableActivityTracking'); - window.snowplow('disableActivityTrackingCallback'); - window.snowplow('flushBuffer'); - window.snowplow('clearUserData'); - window.snowplow = undefined - return - } - - ;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[]; p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments) };p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1; n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script",'/foundation/assets/qdyrnotslk.js',"snowplow")); - - const devMode = localStorage.getItem('snowplowDebug') === 'true' - window.snowplow('newTracker', 'sp1', 'https://c.cookidoo.fr', { - appId: 'cookidoo', - ...(devMode ? { - eventMethod: 'get', - credentials: 'omit', - } : {}), - discoverRootDomain: true, - cookieSameSite: 'Lax', - contexts: { - session: true, - performanceTiming: true, - }, - plugins: [] - }); - - if (window.snowplowReady) { - window.snowplowResolve && window.snowplowResolve() - return - } - window.snowplowReady = new Promise(r => r()) - }) - </script> - - -<style scoped> - @media only screen and (min-width: 1333px) { - .footer-copyright { - margin-bottom: 1.5rem; - margin-top: -1.5rem; - } - } -</style> - - - - </div> - <core-lazy-loading></core-lazy-loading> - <script crossorigin="anonymous" src="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/pl-core-29.0.0-ef7c31e69a40dfd9073ff607719ae635.js"></script> - <script crossorigin="anonymous" src="https://recipepublic-all.prod.external.eu-tm-prod.vorwerk-digital.com/bundle-16dfe1fa4c43ce84038ae2662c3667c0.js"></script> - <script crossorigin="anonymous" src="https://patternlib-all.prod.external.eu-tm-prod.vorwerk-digital.com/pl-recipe-2.18.1-8028f06cc62cca3548570453f00bad42.js"></script> -</body> -</html>
A server.tsx

@@ -0,0 +1,67 @@

+import { Hono } from "hono"; +import { getGroceryList, getRecipesList, storeRecipe } from "./services.ts"; +import db from "./db.ts"; +import { Layout } from "./web/tsx/layout.tsx"; +import { AddRecipe, GroceryList, RecipesList } from "./web/tsx/components.tsx"; +import { serveStatic } from "hono/deno"; +import { basicAuth } from "hono/basic-auth"; + +const app = new Hono(); + +app.use("/style.css", serveStatic({ path: "./web/static/style.css" })); + +app.use( + "/*", + basicAuth({ + username: "admin", + password: "admin", + }) +); + +app.get("/", (c) => { + const recipes = getRecipesList(db); + return c.html( + <Layout url={c.req.url}> + <AddRecipe /> + <RecipesList recipes={recipes} /> + </Layout> + ); +}); + +app.get("/grocery", (c) => { + const url = new URL(c.req.url); + const recipeIds = + url.searchParams.get("recipeIds")?.split(",").map(Number) || []; + const recipes = getRecipesList(db, recipeIds); + console.log({ recipes }); + const ingredients = getGroceryList(db, recipeIds); + return c.html( + <Layout url={c.req.url}> + <GroceryList recipes={recipes} ingredients={ingredients} /> + </Layout> + ); +}); + +app.post("/add-recipe", async (c) => { + const body = await c.req.formData(); + const recipeId = body.get("recipeId"); + if (!recipeId) return c.redirect("/"); + try { + const result = await storeRecipe(db, recipeId as string); + console.log(result); + return c.redirect("/?msg=Recipe added successfully"); + } catch (error) { + console.error(error); + return c.redirect("/?error=An error occured"); + } +}); + +app.post("/add-grocery-list", async (c) => { + const body = await c.req.formData(); + const enabledRecipeIds = [...body.entries()] + .filter(([_, value]) => value === "on") + .map(([key]) => key); + return c.redirect(`/grocery?recipeIds=${enabledRecipeIds.join(",")}`); +}); + +export default app;
A services.ts

@@ -0,0 +1,83 @@

+import type { DatabaseSync } from "node:sqlite"; +import { callLLM } from "./utils/llm.ts"; +import { decodeHtmlChars } from "./utils/html.ts"; + +export const getRecipesList = (db: DatabaseSync, ids?: number[]): Recipe[] => { + if (ids) + return db + .prepare( + `SELECT * FROM recipes WHERE id IN (${ids.map(() => "?").join(",")})` + ) + .all(...ids); + else return db.prepare("SELECT * FROM recipes").all(); +}; + +export const storeRecipe = async (db: DatabaseSync, recipeId: string) => { + let recipeUrl = ""; + if (!recipeId) throw new Error("Please provide a recipe ID"); + else if (recipeId.startsWith("https://")) recipeUrl = recipeId; + else recipeUrl = `https://cookidoo.fr/recipes/recipe/fr-FR/${recipeId}`; + + const recipePage = await fetch(recipeUrl).then((res) => res.text()); + const jsonLdTag = recipePage.match( + /<script type="application\/ld\+json">(.*?)<\/script>/s + )?.[1]; + if (!jsonLdTag) throw new Error("No JSON-LD tag found"); + + const recipeJson = JSON.parse(jsonLdTag); + + const existingItem = db + .prepare("SELECT * FROM recipes WHERE url = ?") + .get(recipeUrl); + if (existingItem) { + console.warn("Recipe already exists in db"); + return null; + } + + const rawIngredientsJson = await callLLM(` + À partir de la liste suivante, renvoi un objet JSON avec unit, quantity, name pour chacun des éléments. + ${decodeHtmlChars(recipeJson.recipeIngredient.join("\n"))} + Si la quantité est un nombre décimal, convertis le. + Utilise de préférences les unités les plus courantes comme g, pincée, cuillère à café, gousse. + Les unités doivent toujours être au singulier. + `); + + const recipe = { + name: recipeJson.name, + ingredients: rawIngredientsJson, + imageUrl: recipeJson.image, + url: recipeUrl, + }; + + const storedRecipe = db + .prepare( + `INSERT INTO recipes (url, name, ingredients, imageUrl) VALUES (?, ?, ?, ?)` + ) + .run(recipe.url, recipe.name, recipe.ingredients, recipe.imageUrl); + + console.log(`New recipe "${recipe.name}" added to db.`); + + return storedRecipe; +}; + +export const getGroceryList = ( + db: DatabaseSync, + recipeIds: number[] +): GroceryItem[] => { + const recipes = db + .prepare(`SELECT * FROM recipes WHERE id IN (${recipeIds.join(",")})`) + .all(); + + const rawIngredients = recipes.flatMap((recipe) => + JSON.parse(recipe.ingredients as string) + ); + + return rawIngredients.reduce((acc, ingredient) => { + const existingIngredient = acc.find( + (i) => i.name === ingredient.name && i.unit === ingredient.unit + ); + if (existingIngredient) existingIngredient.value += ingredient.value; + else acc.push(ingredient); + return acc; + }, [] as { name: string; value: number; unit: string }[]); +};
A types.d.ts

@@ -0,0 +1,14 @@

+type Recipe = { + id: number; + url: string; + name: string; + ingredients: GroceryItem[]; + imageUrl: string; + createdAt: string; +}; + +type GroceryItem = { + name: string; + quantity: number; + unit: string; +};
A web/static/style.css

@@ -0,0 +1,139 @@

+:root { + --rosewater: #dc8a78; + --flamingo: #dd7878; + --pink: #ea76cb; + --mauve: #8839ef; + --red: #d20f39; + --maroon: #e64553; + --peach: #fe640b; + --yellow: #df8e1d; + --green: #40a02b; + --teal: #179299; + --sky: #04a5e5; + --sapphire: #209fb5; + --blue: #1e66f5; + --lavender: #7287fd; + --text: #4c4f69; + --subtext1: #5c5f77; + --subtext0: #6c6f85; + --overlay2: #7c7f93; + --overlay1: #8c8fa1; + --overlay0: #9ca0b0; + --surface2: #acb0be; + --surface1: #bcc0cc; + --surface0: #ccd0da; + --base: #eff1f5; + --mantle: #e6e9ef; + --crust: #dce0e8; + --inverted-text: var(--base); + --selection-color: var(--blue); + --color-primary: var(--lavender); + --color-bg: var(--base); + --color-primary-light: color-mix( + in srgb, + var(--color-primary) 20%, + var(--color-bg) + ); + --text-primary: var(--text); + --text-secondary: var(--subtext1); + + --border-radius: 8px; +} + +body { + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace, serif; + background-color: var(--color-bg); + color: var(--text-primary); +} + +main { + max-width: 40rem; + margin: 0 auto; + padding: 2rem 0 4rem; +} + +h1 { + font-size: 1.5rem; + line-height: 2rem; +} + +h2 { + font-size: 1.25rem; + line-height: 1.75rem; + margin-bottom: 1rem; +} + +p { + margin: 1.5rem 0; + line-height: 1.5; +} + +ul { + line-height: 1.3; +} + +button { + background-color: var(--color-primary); + color: var(--base); + border-radius: var(--border-radius); + border: none; + padding: 0.5rem 1rem; + cursor: pointer; + + input + & { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + &:hover { + opacity: 0.7; + } +} + +input { + border-radius: var(--border-radius); + padding: 0.4rem; + + &:has(+ button) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +} + +a { + color: var(--color-primary); + text-decoration: none; +} + +a:hover { + color: #818cf8; + text-decoration: underline; +} + +.message { + background-color: var(--color-primary-light); + border-radius: var(--border-radius); + padding: 1rem; + + &.error { + background-color: color-mix(in srgb, var(--red) 20%, var(--color-bg)); + } +} + +ul { + padding-left: 0; +} + +li { + &:has(> input[type="checkbox"]) { + margin-top: 1rem; + list-style: none; + + & input { + margin-right: 1rem; + width: 1rem; + height: 1rem; + } + } +}
A web/tsx/components.tsx

@@ -0,0 +1,70 @@

+import type { FC } from "hono/jsx"; + +export const Message: FC<{ url: string }> = (props) => { + const url = new URL(props.url); + const msg = url.searchParams.get("msg"); + const error = url.searchParams.get("error"); + if (error) return <p class="message error">{error}</p>; + else if (msg) return <p class="message">{msg}</p>; + else return null; +}; + +export const AddRecipe: FC = () => { + return ( + <form method="post" action="/add-recipe"> + <h2>Ajouter une recette</h2> + <label> + <input + type="text" + name="recipeId" + placeholder="URL ou ID de la recette Cookidoo" + /> + <button type="submit">Ajouter</button> + </label> + </form> + ); +}; + +export const RecipesList: FC<{ recipes: Recipe[] }> = (props) => { + return ( + <form method="post" action="/add-grocery-list"> + <h2>Liste des recettes</h2> + <ul> + {props.recipes.map((recipe) => ( + <li> + <input type="checkbox" name={`${recipe.id}`} /> + <a href={recipe.url} target="_blank"> + {recipe.name} + </a> + </li> + ))} + </ul> + <button type="submit">Créer une liste de course</button> + </form> + ); +}; + +export const GroceryList: FC<{ + recipes: Recipe[]; + ingredients: GroceryItem[]; +}> = (props) => { + return ( + <div> + <h2>Recettes:</h2> + <ul> + {props.recipes.map((recipe) => ( + <li>{recipe.name}</li> + ))} + </ul> + <h2>Liste des ingrédients:</h2> + <ul> + {props.ingredients.map(({ name, quantity, unit }) => ( + <li> + <input type="checkbox" /> + <strong>{name}</strong>: {quantity} {unit} + </li> + ))} + </ul> + </div> + ); +};
A web/tsx/layout.tsx

@@ -0,0 +1,21 @@

+import type { Child, FC } from "hono/jsx"; +import { Message } from "./components.tsx"; + +export const Layout: FC<{ url: string; children: Child }> = (props) => { + return ( + <html> + <head> + <link href="/style.css" rel="stylesheet" /> + </head> + <body> + <main> + <a href="/"> + <h1>Momix</h1> + </a> + <Message url={props.url} /> + {props.children} + </main> + </body> + </html> + ); +};