PatchHub

← patchhub/recipe-box

Print-friendly view for single recipes

Published ✓ Safe by @priya 0 users1 vote 2 files changed (+104 −6)

Adds a "Print view" button on each recipe that opens a clean black-on-white page showing only that recipe, with tick boxes next to ingredients and numbered steps. You can print it or save it as a PDF, then go back with a button or the Esc key.

What it does

- In the recipe detail view, replace the "🖨 Print" button with a "🖨 Print view" button (tooltip: "Show a clean, printer-friendly version of this recipe"). Clicking it navigates to a new "print" view for that recipe instead of calling window.print() directly. - Print view layout: a toolbar at top with a "← Back to recipe" button (returns to the detail view), a hint text "This is how your recipe will look on paper.", and a primary "🖨 Print" button that calls window.print(). - Below the toolbar, show a paper-like sheet (max width 8.5in, white background, black text, serif font such as Georgia, 12pt, subtle shadow on screen) containing: recipe title as H1; description in italics (if present); a meta line with "Serves: …" and "Time: …" (each only if set); tags joined with " · " (if any); an "Ingredients" heading followed by a two-column list (one column on narrow screens) where each item has a small empty square checkbox drawn before it; a "Steps" heading followed by a numbered list; and a footer line "From my Recipe Box" right-aligned. - If a recipe has no ingredients or steps, show "No ingredients listed." or "No steps listed." in italics. - In print view, hide the site header and use a light grey page background on screen. Headings avoid page breaks right after them, and individual ingredients and steps avoid splitting across pages. - When printing: hide the header, action buttons, toolbar and anything marked no-print (including the storage warning banner); use a white background, 15mm page margins, and remove the sheet's shadow, padding and max-width and the main container's padding. - While in print view, set the browser tab/document title to "<recipe title> – Recipe Box" (so saved PDFs are named after the recipe); otherwise the title is "Recipe Box". - Pressing Escape while in print view returns to the recipe detail view. - Update the README to replace the "Edit, delete or print" bullet with "Edit or delete" and add a "Print" bullet describing the print view, the Print button, and Back/Esc.

Safety check ✓ Safe

The changes only affect page layout, styling and a view switch in the single recipe page, plus a README update. There are no new dependencies, network requests, or data access beyond the recipe already being shown.

Conversation that created this patch
@priya
Add a print-friendly view for a single recipe.
AI
I've added a print-friendly page for each recipe. **How it works:** Open any recipe and click the **🖨 Print view** button (it used to say just "Print"). The recipe then shows up as a clean white page, the way it will look on paper: - The menus, search bar and buttons are gone. - The text is black on white, so it's easy to read in the kitchen. - Ingredients are in two columns, each with a small tick box you can mark with a pen. - Steps are numbered. Click **🖨 Print** to print it, or to save it as a PDF from your browser's print window. Click **← Back to recipe** or press Esc to go back. **Good to know:** - The printout is labelled with the recipe's name, so a saved PDF will usually be named after the recipe too. - Where it can, the page avoids splitting a single step or ingredient across two sheets. - I also updated the instructions file to describe the new print view. I couldn't open a real browser here, so I couldn't check how the page actually looks or prints. I did run a test that clicked through to the print view, pressed Print, and returned with Esc, and all of that worked.
Code changes (for the curious, 2 files) · raw
README.md +5 −1
@@ -24,7 +24,11 @@ That's it.
descriptions, tags, ingredients and steps.
- **Filter by tag**: click a tag (like "dinner" or "vegetarian") above the list.
- **While cooking**: tick off ingredients as you gather them.
−- **Edit, delete or print** a recipe using the buttons at the top of the recipe.
+- **Edit or delete** a recipe using the buttons at the top of the recipe.
+- **Print**: open a recipe and click **🖨 Print view**. You'll see a clean,
+ black-and-white page with just the recipe (no buttons or menus), with little
+ tick boxes next to each ingredient. Click **🖨 Print** to print it or save it
+ as a PDF, and **← Back to recipe** (or press Esc) to return.
## Where are my recipes saved?
index.html +99 −5
@@ -146,10 +146,59 @@
background: #fff4d6; border: 1px solid #f0d58a; border-radius: var(--radius);
padding: 10px 14px; margin-bottom: 16px; font-size: 0.95rem;
}
+ /* ---------- Print-friendly view ---------- */
+ body.print-mode { background: #e9e6e1; }
+ body.print-mode header { display: none; }
+ .print-toolbar {
+ max-width: 8.5in; margin: 0 auto 16px;
+ display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
+ }
+ .print-toolbar .spacer { flex: 1; }
+ .print-toolbar .hint { color: var(--muted); font-size: 0.9rem; }
+ .sheet {
+ max-width: 8.5in; margin: 0 auto;
+ background: #fff; color: #000;
+ padding: 0.8in 0.9in;
+ box-shadow: 0 2px 12px rgba(0,0,0,.15);
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: 12pt; line-height: 1.45;
+ }
+ @media (max-width: 700px) { .sheet { padding: 24px 20px; } }
+ .sheet h1 { font-size: 22pt; margin: 0 0 4px; line-height: 1.2; }
+ .sheet .p-desc { font-style: italic; margin: 0 0 8px; }
+ .sheet .p-meta { display: flex; flex-wrap: wrap; gap: 4px 20px; font-size: 10.5pt; margin: 0 0 4px; }
+ .sheet .p-meta b { font-weight: 700; }
+ .sheet .p-tags { font-size: 10pt; color: #444; margin: 0; }
+ .sheet h2 {
+ font-size: 13pt; text-transform: uppercase; letter-spacing: .06em;
+ border-bottom: 1.5px solid #000; padding-bottom: 3px; margin: 22px 0 10px;
+ break-after: avoid; page-break-after: avoid;
+ }
+ .sheet ul.p-ingredients { list-style: none; padding: 0; margin: 0; columns: 2; column-gap: 36px; }
+ @media (max-width: 600px) { .sheet ul.p-ingredients { columns: 1; } }
+ .sheet ul.p-ingredients li {
+ display: flex; gap: 8px; padding: 2px 0;
+ break-inside: avoid; page-break-inside: avoid;
+ }
+ .sheet ul.p-ingredients li::before {
+ content: ""; flex: 0 0 auto; width: 10px; height: 10px;
+ border: 1px solid #000; margin-top: 0.42em;
+ }
+ .sheet ol.p-steps { padding-left: 24px; margin: 0; }
+ .sheet ol.p-steps li { margin-bottom: 8px; padding-left: 4px; break-inside: avoid; page-break-inside: avoid; }
+ .sheet .p-empty { font-style: italic; color: #444; margin: 0; }
+ .sheet .p-footer {
+ margin-top: 28px; padding-top: 6px; border-top: 1px solid #bbb;
+ font-size: 9pt; color: #555; text-align: right;
+ }
+
@media print {
− header, .actions, .no-print { display: none !important; }
− body { background: #fff; }
+ @page { margin: 15mm; }
+ header, .actions, .no-print, .print-toolbar { display: none !important; }
+ body, body.print-mode { background: #fff; }
+ main { padding: 0; max-width: none; }
.panel { border: none; padding: 0; }
+ .sheet { box-shadow: none; padding: 0; margin: 0; max-width: none; }
}
</style>
</head>
@@ -338,11 +387,15 @@
function render() {
app.innerHTML = "";
if (!storageOk) {
− app.appendChild(el("div", { class: "warning", text:
+ app.appendChild(el("div", { class: "warning no-print", text:
"Heads up: your browser isn't letting this page save data, so changes will be lost when you close it. " +
"Try opening the file in a regular (not private) window or a different browser." }));
}
− if (state.view === "detail" && find(state.id)) renderDetail(find(state.id));
+ var printing = state.view === "print" && !!find(state.id);
+ document.body.classList.toggle("print-mode", printing);
+ document.title = printing ? find(state.id).title + " – Recipe Box" : "Recipe Box";
+ if (printing) renderPrint(find(state.id));
+ else if (state.view === "detail" && find(state.id)) renderDetail(find(state.id));
else if (state.view === "form") renderForm(state.id ? find(state.id) : null);
else { state.view = "list"; renderList(); }
}
@@ -435,7 +488,7 @@
var actions = el("div", { class: "actions" }, [
el("button", { text: "← All recipes", onclick: function () { go("list"); } }),
el("span", { class: "spacer" }),
− el("button", { text: "🖨 Print", onclick: function () { window.print(); } }),
+ el("button", { text: "🖨 Print view", title: "Show a clean, printer-friendly version of this recipe", onclick: function () { go("print", r.id); } }),
el("button", { text: "✏️ Edit", onclick: function () { go("form", r.id); } }),
el("button", { class: "danger", text: "🗑 Delete", onclick: function () {
if (confirm('Delete "' + r.title + '"? This can\'t be undone.')) {
@@ -480,6 +533,42 @@
]));
}
+ // ---------- Print-friendly view ----------
+ function renderPrint(r) {
+ var toolbar = el("div", { class: "print-toolbar" }, [
+ el("button", { text: "← Back to recipe", onclick: function () { go("detail", r.id); } }),
+ el("span", { class: "spacer" }),
+ el("span", { class: "hint", text: "This is how your recipe will look on paper." }),
+ el("button", { class: "primary", text: "🖨 Print", onclick: function () { window.print(); } })
+ ]);
+
+ var meta = [];
+ if (r.servings) meta.push(el("span", null, [el("b", { text: "Serves: " }), r.servings]));
+ if (r.time) meta.push(el("span", null, [el("b", { text: "Time: " }), r.time]));
+
+ var ingredients = r.ingredients || [];
+ var stepsList = r.steps || [];
+
+ var sheet = el("article", { class: "sheet" }, [
+ el("h1", { text: r.title }),
+ r.description ? el("p", { class: "p-desc", text: r.description }) : null,
+ meta.length ? el("div", { class: "p-meta" }, meta) : null,
+ (r.tags && r.tags.length) ? el("p", { class: "p-tags", text: r.tags.join(" · ") }) : null,
+ el("h2", { text: "Ingredients" }),
+ ingredients.length
+ ? el("ul", { class: "p-ingredients" }, ingredients.map(function (i) { return el("li", { text: i }); }))
+ : el("p", { class: "p-empty", text: "No ingredients listed." }),
+ el("h2", { text: "Steps" }),
+ stepsList.length
+ ? el("ol", { class: "p-steps" }, stepsList.map(function (s) { return el("li", { text: s }); }))
+ : el("p", { class: "p-empty", text: "No steps listed." }),
+ el("div", { class: "p-footer", text: "From my Recipe Box" })
+ ]);
+
+ app.appendChild(toolbar);
+ app.appendChild(sheet);
+ }
+
// ---------- Add / edit form ----------
function renderForm(r) {
var editing = !!r;
@@ -575,6 +664,11 @@
render();
});
+ // Esc leaves the print view
+ document.addEventListener("keydown", function (e) {
+ if (e.key === "Escape" && state.view === "print") go("detail", state.id);
+ });
+
// Keep multiple open tabs in sync
window.addEventListener("storage", function (e) {
if (e.key === STORAGE_KEY) {