From 597a0482af8088fa351b536c462954c200616146 Mon Sep 17 00:00:00 2001 From: Nicholas Jitkoff Date: Wed, 15 Jun 2022 07:50:03 -0700 Subject: [PATCH] Add qr codes to recipes --- docs/render.js | 8 +++ docs/render/recipe.css | 141 +++++++++++++++++++++++++++++------------ docs/render/recipe.js | 91 ++++++++++++++------------ 3 files changed, 157 insertions(+), 83 deletions(-) diff --git a/docs/render.js b/docs/render.js index 7bea2dc..4926af4 100644 --- a/docs/render.js +++ b/docs/render.js @@ -39,3 +39,11 @@ function renderScriptContent(data, origin) { window.addEventListener("message", function(e) { renderScriptContent(e.data, e.origin); }, false); + +function QRCodeURL(url, options) { + let size = options?.size ?? 547; + let errorCorrection = options?.correction ?? 'L'; + let margin = options?.margin?.toString() || "1"; + console.log("margin", options?.margin); + return `https://chart.googleapis.com/chart?cht=qr&chs=${size}x${size}&chld=${errorCorrection}|${margin}&choe=UTF-8&chl=${encodeURIComponent(url || location.href)}`; +} \ No newline at end of file diff --git a/docs/render/recipe.css b/docs/render/recipe.css index cf000c3..dd53b30 100644 --- a/docs/render/recipe.css +++ b/docs/render/recipe.css @@ -7,23 +7,27 @@ body { background-color: var(--background-color); color: var(--text-color); - --text-color:rgba(0,0,0,0.87); + --text-color: + rgba(0,0,0,0.87); --opaque-text-color:black; - --disabled-text-color:rgba(16, 12, 12, 0.54); + --disabled-text-color: + rgba(16, 12, 12, 0.54); + --hover-background-color: + rgba(16, 12, 12, 0.07); --background-color:white; - } .thumbnail { width: 100%; - padding-top:28.125%; + padding-top: 77%; max-height:1vh; background-color: gray; background-size: cover; background-position: center; margin-bottom:2em; box-sizing: border-box; -/* margin: 0 -2em; */} + /* margin: 0 -2em; */ + } .ingredients:hover .noun { /* text-decoration:underline; */ @@ -96,7 +100,8 @@ ul.step { span.number { margin-left: -27px; - float:left; + float: + left; border: 1.5px solid var(--text-color); font-weight: 800; border-radius:10em; @@ -105,9 +110,9 @@ span.number { text-align: center; font-size: 10px; line-height: 16px; - opacity:0.7; + opacity: 0.7; user-select: none; -/* background: black; */ + /* background: black; */ } .complete span.number, @@ -184,16 +189,27 @@ li:hover { } .ingredients { - font-weight: 500; - font-size: 90%; + font-weight: 600; + font-size: 80%; /* position:sticky; */ /* top: 0px; */ /* padding-top: 1em; */ flex: 0 1 35%; -/* color: rgba(0,0,0,0.8); */} +/* color: rgba(0,0,0,0.8); */ +/* color:rgb(0, 118, 173); */ + +} .noun { - font-weight: 700; + font-weight: 600; color: var(--opaque-text-color); + +} +.complete .noun { + color:var(--disabled-text-color); +} +li:hover .noun { + text-decoration: 1px dotted underline; + /* background-color: var(--hover-background-color); */ } .instructions { @@ -257,11 +273,12 @@ text-decoration: rgba(0,0,0,0.75) solid 0.5px line-through; /* Ignored in CSS1/C } .instructions hr { - width: 100px; + width: 5em; height: 0.5px; background-color: currentColor; - opacity: .3; - border:none; + opacity: 1; + border: + none; margin: 0.25em 0; } @@ -276,30 +293,37 @@ hr { opacity: 0.54; */ } -a.action { - /* border: none; - background: none; - text-transform: uppercase; - text-decoration: none; - color: currentColor; - opacity: 0.; - cursor: pointer; - border: 1px solid currentColor; - padding: 0 0.6em; - border-radius: 1em; - /* line-height: 16px; - font-size: 14px; - box-sizing: border-box; - line-height: 17px; */ - border-radius:1em; - cursor:pointer; +.actions { + display: flex; + gap: 0.5em 1em; + line-height:24px; + margin-left: -4px; } +a.action { + border-radius:50%; + cursor: pointer; + width: 1.5em; + height: 1.5em; + /* display: flex; + align-items: center; + justify-content: center; */ + text-align: center; + line-height: 1.5em; + background-color: + var(--hover-background-color); +} + +a.action:hover { + background-color:var(--hover-background-color); +} a.action:hover:not(:active) .icon { opacity:1.0; - /* text-decoration: none; - box-shadow: 0 1px 1px currentColor; - margin-top: -1px; */ +} + +a.action .icon { + margin-right: 0; + opacity: 1.0; } @media screen and (max-width: 480px) { @@ -335,22 +359,21 @@ a.action:hover:not(:active) .icon { } .metadata .icon { -font-size: 15px; -vertical-align: middle; -margin-top: -4px; -margin-right: 4px; -opacity:0.6; + font-size: 15px;vertical-align: middle;margin-top: -4px;margin-right: 4px;opacity: .6; color:var(--text-color); +user-select: none; } @media (prefers-color-scheme: dark) { body { --text-color:white; + --hover-background-color:rgba(255,255,255, 0.14); --background-color:#222; } } + @media print { body { font-size: 14px; @@ -360,6 +383,40 @@ color:var(--text-color); } } -.instructions.numbered hr {margin: 0.7em 0;height: 1.5px;opacity: 10;} +@media not print { + .print-only { + display:none; + } +} -.instructions.numbered {/* margin-top: -1.6em; */} \ No newline at end of file +.instructions.numbered hr {margin: 0.7em -1px;height: 1.5px;opacity: 0.2;width: auto;} + +.instructions.numbered {/* margin-top: -1.6em; */} + + +img.qr { + width:100%; +} + +@media screen and (min-width: 1000px) { + body { + width:auto; + margin:0; + } + .recipe { + flex-direction: row; + display: flex; + max-width: unset; + } + + #thumbnail-container {width: 100%;height: 100vh;} + + .thumbnail { + max-height: 100vh; + height: 100vh; + } + .recipe article { + max-width:50em; + padding: 4em; + } +} \ No newline at end of file diff --git a/docs/render/recipe.js b/docs/render/recipe.js index 32937a3..c1fa2a5 100644 --- a/docs/render/recipe.js +++ b/docs/render/recipe.js @@ -25,8 +25,8 @@ FRACTION_MAP = { } let ignoredTerms = [ - "teaspoon", "teaspoons", "tablespoon", "tablespoons", "cup", "cups", "taste", "more", "melted", "into", "wide", "pound", "pounds", "gram", "grams", "you", "ounce", "ounces", "thinly", "sliced", - "pan", "finely", "ground", "garnish", "about", "cut", "and", "smashed", "each", "the", "medium", "large", "small", "for", "chopped", "minced", "grated", "box", "softened", "directed", "shredded", "cooked", "from", "frozen", "thawed" + "note", "teaspoon", "teaspoons", "tablespoon", "tablespoons", "cup", "cups", "taste", "more", "melted", "into", "wide", "pound", "pounds", "gram", "grams", "you", "ounce", "ounces", "thinly", "sliced", + "pan", "cube", "cubes", "finely", "ground", "garnish", "about", "cut", "and", "smashed", "each", "the", "medium", "large", "small", "for", "chopped", "minced", "grated", "box", "softened", "directed", "shredded", "cooked", "from", "frozen", "thawed" ] const replacements = { @@ -142,7 +142,7 @@ function highlightStep(e) { } -const ingredientMatch = /^(?:A )?([\/0-9 \u00BC-\u00BE\u2153-\u215E\u2009]*) (.*)/ +const ingredientMatch = /^(?:A )?([\/0-9 \u00BC-\u00BE\u2153-\u215E\u2009]*)\s(.*)/ function ingredientEl(string, terms) { @@ -164,6 +164,10 @@ function highlightTerms(string, terms) { return string.replace(pattern, match => `${match}`); } +function share() { + parent.postMessage({share:{}}, "*"); +} + function render() { delete document.documentElement.style.display; document.body.childNodes.forEach((c) => document.body.removeChild(c)) @@ -286,50 +290,55 @@ function render() { document.body.appendChild( - m("article.recipe", {}, + m(".recipe", {}, image ? m("#thumbnail-container", m("#thumbnail.thumbnail.noprint", { style: "background-image:url(" + ");", onload: imgload })) : null, - - m("header", - m("img.publisher", { src: json.publisher?.image ?.[0]?.url ?? json.publisher ?.logo ?.url }), - m("h1", title), - m(".metadata", - m("div", m("span.yield", m(".icon.material-icons-outlined", "restaurant"), yield)), - m(".time", - m(".icon.material-icons-outlined", "timer"), + m("article", + m("header", + m("img.publisher", { src: json.publisher?.image ?.[0]?.url ?? json.publisher ?.logo ?.url }), + m("h1", title), + m(".metadata", + m("div", m("span.yield", m(".icon.material-icons-outlined", "restaurant"), yield)), + m(".time", + m(".icon.material-icons-outlined", "timer"), - json.totalTime ? m("span", formatTime(json.totalTime)) : undefined, - // " (", - // json.prepTime ? m("span", formatTime(json.prepTime), " prep") : undefined, - // json.cookTime ? m("span", ", ", formatTime(json.cookTime), " cook") : undefined, - // ")" + json.totalTime ? m("span", formatTime(json.totalTime)) : undefined, + // " (", + // json.prepTime ? m("span", formatTime(json.prepTime), " prep") : undefined, + // json.cookTime ? m("span", ", ", formatTime(json.cookTime), " cook") : undefined, + // ")" + ), + (rating) ? m("div.rating", + m(".icon.material-icons-outlined", "grade"), + parseFloat(rating.ratingValue).toFixed(1), " ", + // ratingCount ? m("span.count", ratingCount.toString()) : null + ) + : null, + json.nutrition?.calories ? + m("div", m(".icon.material-icons-outlined", "info"), + (json.nutrition?.calories.toString().replace("calories", "Cal").replace("kcal", "Cal")) + (isNaN(json.nutrition?.calories) ? '' : ' Cal')) : null, + + m("div.spacer"), + m(".actions", + m("a.action.noprint", { title:"Open original", href: json.mainEntityOfPage || json.url, target:"_blank"}, m(".icon.material-icons-outlined", "public")), + m("a.action.noprint", { title:"Share", onclick: share}, m(".icon.material-icons-outlined", "share")), + m("a.action.noprint", { title:"Show steps as list", href: "#", onclick: () => {reformat = !reformat; render(); return false;}}, m(".icon.material-icons-outlined", "notes")), + m("a.action.noprint", { title:"Print", href: "#", onclick: () => window.print() }, m(".icon.material-icons-outlined", "print")), + ) ), - (rating) ? m("div.rating", - m(".icon.material-icons-outlined", "grade"), - parseFloat(rating.ratingValue).toFixed(1), " ", - // ratingCount ? m("span.count", ratingCount.toString()) : null - ) - : null, - json.nutrition?.calories ? m("div", m(".icon.material-icons-outlined", "info"), (json.nutrition?.calories) + (parseFloat(json.nutrition?.calories) != NaN ? " calories" : "")) : null, + json.description ? m(".description", + clean(json.description), + json.author?.name ? m("span.author", (" —⁠" + json.author?.name)) : null, - m("div.spacer"), - m("a.action.noprint", { title:"Open original", href: json.mainEntityOfPage || json.url, target:"_blank"}, m(".icon.material-icons-outlined", "link")), - m("a.action.noprint", { title:"Show steps as list", href: "#", onclick: () => {reformat = !reformat; render(); return false;}}, m(".icon.material-icons-outlined", "format_list_numbered")), - m("a.action.noprint", { title:"Print", href: "#", onclick: () => window.print() }, m(".icon.material-icons-outlined", "print")), + m("p"), + + ) : null, ), - json.description ? m(".description", - clean(json.description), - json.author?.name ? m("span.author", (" —⁠" + json.author?.name)) : null, - - m("p"), - - ) : null, - - ), - m(".columns", - m(".ingredients", ingredients), - m(reformat ? ".instructions.numbered" : ".instructions", instructions) - ), + m("main.columns", + m("section.ingredients", ingredients, m("img.qr.print-only", {src:QRCodeURL(params.originalURL, {margin:0})})), + m(reformat ? "section.instructions.numbered" : "section.instructions", instructions) + ), + ) ) )