Bladeren bron

Create the print recipe page

Lee Morgan 1 week geleden
bovenliggende
commit
ba372e7ef1

+ 2 - 0
src/routes/+layout.svelte

@@ -13,6 +13,7 @@
     <title>Recipes</title>
     <title>Recipes</title>
 </svelte:head>
 </svelte:head>
 
 
+{#if !page.url.pathname.endsWith("/print")}
 <header>
 <header>
     <a class="logo" href="/">
     <a class="logo" href="/">
         <img src={logo} alt="Site logo">
         <img src={logo} alt="Site logo">
@@ -45,6 +46,7 @@
         {/each}
         {/each}
     </div>
     </div>
 </header>
 </header>
+{/if}
 
 
 {@render children()}
 {@render children()}
 
 

+ 11 - 3
src/routes/recipe/[recipeId]/+page.svelte

@@ -84,7 +84,7 @@
             {#if shareOpen}
             {#if shareOpen}
                 <div class="shareMenu" role="menu">
                 <div class="shareMenu" role="menu">
                     <button role="menuitem" onclick={copyLink}>Copy link</button>
                     <button role="menuitem" onclick={copyLink}>Copy link</button>
-                    <button role="menuitem">Print</button>
+                    <a href="/recipe/{data.recipe.id}/print" role="menuitem">Print</a>
                     <button role="menuitem">Download</button>
                     <button role="menuitem">Download</button>
                 </div>
                 </div>
             {/if}
             {/if}
@@ -179,7 +179,11 @@
         box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
         box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
     }
     }
 
 
-    .shareMenu button{
+    .shareMenu button,
+    .shareMenu a{
+        display: flex;
+        align-items: center;
+        box-sizing: border-box;
         border: none;
         border: none;
         outline: none;
         outline: none;
         cursor: pointer;
         cursor: pointer;
@@ -194,9 +198,12 @@
         line-height: 1.2;
         line-height: 1.2;
         text-align: left;
         text-align: left;
         white-space: nowrap;
         white-space: nowrap;
+        text-decoration: none;
+        font-family: inherit;
     }
     }
 
 
-    .shareMenu button:hover{
+    .shareMenu button:hover,
+    .shareMenu a:hover{
         background: var(--color-surface-alt);
         background: var(--color-surface-alt);
     }
     }
 
 
@@ -366,6 +373,7 @@
         .buttonBox > button,
         .buttonBox > button,
         .shareToggle,
         .shareToggle,
         .shareMenu button,
         .shareMenu button,
+        .shareMenu a,
         .missing .button{
         .missing .button{
             min-height: 44px;
             min-height: 44px;
         }
         }

+ 23 - 0
src/routes/recipe/[recipeId]/print/+page.server.js

@@ -0,0 +1,23 @@
+import auth from "$lib/server/auth.js";
+import Recipe from "$lib/server/models/Recipe.js";
+
+export async function load({cookies, params}){
+    const user = await auth(cookies);
+
+    let recipe;
+    try{
+        recipe = await Recipe.findOne({_id: params.recipeId}, {"ingredients._id": 0}).lean();
+
+        if(recipe.private && user._id.toString() !== recipe.user.toString()){
+            recipe = null;
+        }else{
+            recipe.id = recipe._id.toString();
+            recipe._id = undefined;
+            recipe.user = undefined;
+        }
+    }catch{
+        recipe = null;
+    }
+
+    return {recipe};
+}

+ 376 - 0
src/routes/recipe/[recipeId]/print/+page.svelte

@@ -0,0 +1,376 @@
+<script>
+    let {data} = $props();
+    let recipe = $derived(data.recipe);
+
+    function printPage(){
+        window.print();
+    }
+</script>
+
+<svelte:head>
+    <title>{recipe ? `${recipe.name}` : "Recipe"}</title>
+</svelte:head>
+
+{#if recipe}
+    <div class="screenBar">
+        <a href="/recipe/{recipe.id}">Back to recipe</a>
+        <button type="button" onclick={printPage}>Print</button>
+    </div>
+
+    <article class="sheet">
+        <div class="masthead">
+            <h1>{recipe.name}</h1>
+        </div>
+
+        <div class="layout">
+            <section class="ingredients">
+                <h2>Ingredients</h2>
+                <ul>
+                    {#each recipe.ingredients as i}
+                        <li>
+                            <span class="qty">{i.quantity}</span>
+                            <span class="unit">{i.unit}</span>
+                            <span class="iname">{i.name}</span>
+                        </li>
+                    {/each}
+                </ul>
+            </section>
+
+            <section class="method">
+                <h2>Method</h2>
+                <ol>
+                    {#each recipe.steps as s, idx}
+                        <li>
+                            <span class="num">{idx + 1}</span>
+                            <p>{s}</p>
+                        </li>
+                    {/each}
+                </ol>
+            </section>
+
+            {#if recipe.notes}
+                <section class="notes">
+                    <h2>Notes</h2>
+                    <p>{recipe.notes}</p>
+                </section>
+            {/if}
+        </div>
+    </article>
+{:else}
+    <div class="missing">
+        <h1>Recipe doesn't exist or is private</h1>
+        <p>It may have been removed, or you don't have permission to view it.</p>
+        <a href="/dashboard" class="button">Back to Dashboard</a>
+    </div>
+{/if}
+
+<style>
+    :global(html),
+    :global(body){
+        background: var(--color-bg);
+    }
+
+    .screenBar{
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        gap: 0.75rem;
+        width: min(8.5in, calc(100% - 2rem));
+        margin: 1rem auto 0;
+    }
+
+    .screenBar a,
+    .screenBar button{
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        margin: 0;
+        border: none;
+        border-radius: 6px;
+        padding: 0.55rem 1.1rem;
+        background: var(--color-primary);
+        color: var(--color-text-on-primary);
+        font: inherit;
+        font-weight: 600;
+        font-size: 0.95rem;
+        text-decoration: none;
+        cursor: pointer;
+    }
+
+    .screenBar a:hover,
+    .screenBar button:hover{
+        background: var(--color-primary-hover);
+    }
+
+    .sheet{
+        width: min(8.5in, calc(100% - 2rem));
+        margin: 1rem auto 2.5rem;
+        padding: 0.7in 0.75in 0.65in;
+        background: #fffdf8;
+        color: #1c1917;
+        border-radius: 2px;
+        box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
+    }
+
+    .masthead{
+        margin-bottom: 1.35rem;
+        padding-bottom: 0.85rem;
+        border-bottom: 2px solid #ff7a1a;
+    }
+
+    h1{
+        margin: 0;
+        font-family: Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
+        font-size: clamp(1.8rem, 1.4rem + 1.6vw, 2.45rem);
+        font-weight: 700;
+        line-height: 1.15;
+        letter-spacing: -0.02em;
+        overflow-wrap: anywhere;
+    }
+
+    .layout,
+    .ingredients,
+    .method,
+    .notes{
+        display: block;
+        width: 100%;
+        max-width: 100%;
+        float: none;
+    }
+
+    .layout{
+        display: flex;
+        flex-direction: column;
+        gap: 1.6rem;
+    }
+
+    h2{
+        margin: 0 0 0.7rem;
+        color: #cc5f0f;
+        font-size: 0.72rem;
+        font-weight: 700;
+        letter-spacing: 0.18em;
+        text-transform: uppercase;
+    }
+
+    .ingredients ul{
+        margin: 0;
+        padding: 0;
+        list-style: none;
+        display: grid;
+        grid-template-columns: 1fr 1fr;
+        gap: 0.45rem 1.5rem;
+        width: 100%;
+    }
+
+    .ingredients li{
+        display: grid;
+        grid-template-columns: 2.1rem 2.2rem minmax(0, 1fr);
+        gap: 0.35rem 0.45rem;
+        align-items: baseline;
+        min-width: 0;
+        padding: 0.2rem 0 0.2rem 0.6rem;
+        border-left: 2px solid #ff7a1a;
+        font-size: 0.95rem;
+        line-height: 1.35;
+        break-inside: avoid;
+    }
+
+    .qty{
+        font-variant-numeric: tabular-nums;
+        font-weight: 700;
+        text-align: right;
+    }
+
+    .unit{
+        color: #6b645c;
+        font-size: 0.85rem;
+    }
+
+    .iname{
+        overflow-wrap: anywhere;
+    }
+
+    .method{
+        width: 100%;
+        padding-top: 0.15rem;
+        border-top: 1px solid #e6dfd4;
+    }
+
+    .method ol{
+        margin: 0;
+        padding: 0;
+        list-style: none;
+        display: flex;
+        flex-direction: column;
+        gap: 0.95rem;
+        counter-reset: none;
+    }
+
+    .method li{
+        display: grid;
+        grid-template-columns: 1.55rem minmax(0, 1fr);
+        gap: 0.85rem;
+        align-items: start;
+        width: 100%;
+        break-inside: avoid;
+    }
+
+    .num{
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 1.55rem;
+        height: 1.55rem;
+        border: 1.5px solid #ff7a1a;
+        border-radius: 50%;
+        color: #cc5f0f;
+        font-size: 0.75rem;
+        font-weight: 700;
+        line-height: 1;
+    }
+
+    .method p{
+        margin: 0;
+        font-size: 0.98rem;
+        line-height: 1.5;
+        overflow-wrap: anywhere;
+    }
+
+    .notes{
+        padding-top: 0.35rem;
+        border-top: 1px solid #e6dfd4;
+        break-inside: avoid;
+    }
+
+    .notes p{
+        margin: 0;
+        font-size: 0.95rem;
+        line-height: 1.55;
+        white-space: pre-wrap;
+        overflow-wrap: anywhere;
+        color: #3f3a36;
+    }
+
+    @media (max-width: 560px){
+        .ingredients ul{
+            grid-template-columns: 1fr;
+        }
+    }
+
+    .missing{
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        gap: 0.75rem;
+        width: min(420px, calc(100% - 2rem));
+        margin: 15vh auto;
+        padding: 2rem 1.5rem;
+        text-align: center;
+        color: var(--color-text);
+        background: var(--color-surface);
+        border: 1px solid var(--color-border);
+        border-radius: 10px;
+    }
+
+    .missing h1{
+        font-family: inherit;
+        font-size: 1.35rem;
+        letter-spacing: 0;
+    }
+
+    .missing p{
+        margin: 0;
+        color: var(--color-text-muted);
+    }
+
+    .missing a{
+        margin: 0;
+    }
+
+    @page{
+        size: letter;
+        margin: 0.55in 0.6in 0.55in 0.6in;
+    }
+
+    @media print{
+        :global(html),
+        :global(body){
+            background: #ffffff !important;
+            color: #1c1917 !important;
+            height: auto !important;
+            overflow: visible !important;
+        }
+
+        .screenBar{
+            display: none !important;
+        }
+
+        .sheet{
+            width: auto;
+            max-width: none;
+            margin: 0;
+            padding: 0;
+            background: #ffffff;
+            box-shadow: none;
+            border-radius: 0;
+        }
+
+        .layout,
+        .ingredients,
+        .method,
+        .notes{
+            display: block !important;
+            width: 100% !important;
+            max-width: 100% !important;
+            float: none !important;
+            clear: both !important;
+            grid-column: auto !important;
+            grid-row: auto !important;
+            grid-template-columns: none !important;
+        }
+
+        .layout{
+            gap: 0 !important;
+        }
+
+        .ingredients,
+        .method,
+        .notes{
+            margin-top: 1.15rem !important;
+            padding-right: 0 !important;
+            border-right: 0 !important;
+        }
+
+        .ingredients{
+            margin-top: 0 !important;
+        }
+
+        .ingredients ul{
+            display: grid !important;
+            grid-template-columns: 1fr 1fr !important;
+            gap: 0.35rem 0.5in !important;
+            width: 100% !important;
+            float: none !important;
+        }
+
+        .ingredients li{
+            grid-template-columns: 2.1rem 2.2rem minmax(0, 1fr) !important;
+        }
+
+        h1, h2, .masthead, .method li, .ingredients li, .notes{
+            break-inside: avoid;
+            page-break-inside: avoid;
+        }
+
+        .masthead{
+            break-after: avoid;
+            page-break-after: avoid;
+        }
+
+        *{
+            print-color-adjust: exact;
+            -webkit-print-color-adjust: exact;
+        }
+    }
+</style>