From df2bd0c682431b7c6b8cbd17600e5dd451c59923 Mon Sep 17 00:00:00 2001 From: Nicholas Jitkoff Date: Mon, 5 Sep 2022 11:41:33 -0700 Subject: [PATCH] Update Recipe formatting --- .vscode/launch.json | 31 ++++++++++++ .vscode/settings.json | 9 ++++ docs/render/recipe.css | 109 ++++++++++++++++++++++++++++++----------- docs/render/recipe.js | 52 +++++++++++++------- package.json | 3 ++ 5 files changed, 157 insertions(+), 47 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7571bf1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "netlify dev", + "type": "node", + "request": "launch", + "skipFiles": ["/**"], + "outFiles": ["${workspaceFolder}/.netlify/functions-serve/**/*.js"], + "program": "${workspaceFolder}/node_modules/.bin/netlify", + "args": ["dev"], + "console": "integratedTerminal", + "env": { "BROWSER": "none" }, + "serverReadyAction": { + "pattern": "Server now ready on (https?://[\w:.-]+)", + "uriFormat": "%s", + "action": "debugWithChrome" + } + }, + { + "name": "netlify functions:serve", + "type": "node", + "request": "launch", + "skipFiles": ["/**"], + "outFiles": ["${workspaceFolder}/.netlify/functions-serve/**/*.js"], + "program": "${workspaceFolder}/node_modules/.bin/netlify", + "args": ["functions:serve"], + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2ee77e1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "deno.enable": true, + "deno.enablePaths": [ + "netlify/edge-functions" + ], + "deno.unstable": true, + "deno.importMap": ".netlify/edge-functions-import-map.json", + "deno.path": "~/Library/Preferences/netlify/deno-cli/deno" +} \ No newline at end of file diff --git a/docs/render/recipe.css b/docs/render/recipe.css index 64ab707..6b9c7de 100644 --- a/docs/render/recipe.css +++ b/docs/render/recipe.css @@ -176,7 +176,7 @@ ul.step { ul.step:after { content: ""; - display: block; + display: none; width: 5em; height: 0.5px; background-color: currentColor; @@ -198,8 +198,12 @@ ul.step:last-child:after { display: none; } -span.number { - margin-left: -3rem; +.instructions.numbered .step { + margin-bottom:1em; +} + +span.bullet { + margin-left: -1.5rem; float: left; /* border: 1.5px solid var(--text-color); */ @@ -215,11 +219,18 @@ span.number { color: var(--accent-color) /* background: black; */; - display: none; + /* display: none; */ + /* display: block; */ } +.instructions .step:not(:hover) span.bullet.substep { + opacity: 0.3; + color: var(--text-color); +} + + @media screen { - .complete span.number, li:hover span.number { + .complete span.bullet, li:hover span.bullet { color:transparent; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='48' width='48'%3E%3Cpath d='M18.9 36.9 6.5 24.5 9.9 21.1 18.9 30.1 38.05 10.95 41.45 14.35Z'/%3E%3C/svg%3E"); background-size:cover; @@ -343,8 +354,8 @@ li:hover .noun { } .instructions.numbered li { - /* padding-left: 3rem; */ - /* margin-left: 0; */ + padding-left: 2rem; + margin-left: -0.6rem; } .substep { @@ -352,16 +363,16 @@ li:hover .noun { /* display: inline; */ } .ingredient { - padding: .4em 0.4em; - margin: 0 -.4em 1em -.4em; - line-height: 1em; + /* padding: .4em 0.4em; */ + /* margin: 0 -.4em 1em -.4em; */ + line-height: 120%; /* opacity: 0.75; */ cursor: pointer; - margin-bottom:0.2em; + margin-bottom: 0.2em; } .hanging .ingredient { - margin-left: -3rem; - padding-left: 3rem; + /* margin-left: -3rem; */ + /* padding-left: 3rem; */ } .ingredients .yield { @@ -371,14 +382,15 @@ li:hover .noun { margin-left:2em; } .ingredients.hanging .quantity { - float: - left; - margin-left: -3rem; - min-width: 3rem; + /* float: + left; */ + /* margin-left: -3rem; */ + /* max-width: 1rem; */ text-align: right; color: var(--accent-color); - /* font-weight: bold; */ + width: auto; + white-space: nowrap; } .ingredient:hover { @@ -536,8 +548,7 @@ hr { img.qr { /* margin-top:2em; */ - position: - absolute; + position: absolute; bottom:0; left: 0; max-width: 1in; @@ -685,7 +696,8 @@ span.number:after { section.ingredients caption { content: "Ingredients"; - margin-bottom: 1.3rem; + display: table-caption; + margin-bottom: calc(1.3rem - 1em); } .hanging .ingredient .quantity:after { @@ -697,20 +709,25 @@ section.ingredients caption { /* padding-right: 24px; */ margin-bottom: 1.3rem; } - -.ingredients caption, .instructions caption { - text-align: - left; +a.publisherlink, +.ingredients caption, +.instructions caption { font-weight: 600; - margin-bottom: 1em; - display: block; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.01rem; user-select: none; - /* margin: 0; */ color: var(--accent-color); } +.ingredients caption, +.instructions caption { + text-align: left; + display: block; + margin-bottom: 1em; + +} + + .listtoggle { /* float: right; */ @@ -730,6 +747,8 @@ a.publisherlink { /* order: -2; */ /* width: 100%; */ float: left; + text-decoration: none; + color: var(--disabled-text-color); } .headerleft h1 { @@ -779,3 +798,37 @@ span.timer.active.expired { } } +.ingredients { + +} + + +.ingredients { + display: table; + width: auto; + /* background-color: #eee; */ + /* border: 1px solid #666666; */ + border-spacing: 5px; /* cellspacing:poor IE support for this */ + /* border-collapse: separate; */ + border-spacing: 0 1em; + height: auto; + margin-bottom: auto; +} +.ingredient { + display: table-row; + width: auto; + clear: both; +} +.ingredient > * { + /* float: left; */ /* fix for buggy browsers */ + display: table-cell; + width: 100%; + /* background-color: red; */ + /* padding: 0; */ + /* margin: 0; */ + /* margin-bottom: 1em; */ +} + +a.publisherlink:hover { + color: var(--accent-color); +} \ No newline at end of file diff --git a/docs/render/recipe.js b/docs/render/recipe.js index 0cf8435..b157a79 100644 --- a/docs/render/recipe.js +++ b/docs/render/recipe.js @@ -24,7 +24,7 @@ let FRACTION_MAP = { } let ignoredTerms = [ - "broken", "pieces", "tbsp", "tsp", "peeled", "then", "can", "oz", "fresh", "out", "not", "sprig", "sprigs", "room", "temperature", "still", "see", "notes", "with", "beat", "together", "crust", "very", "cold", "hot", "top", "warm", "one", "note", "teaspoon", "teaspoons", "tablespoon", "tablespoons", "cup", "cups", "taste", "more", "melted", "into", "wide", "pound", "pounds", "gram", "grams", "you", "ounce", "ounces", "thinly", "sliced", + "but", "broken", "pieces", "tbsp", "tsp", "peeled", "then", "can", "oz", "fresh", "out", "not", "sprig", "sprigs", "room", "temperature", "still", "see", "notes", "with", "beat", "together", "crust", "very", "cold", "hot", "top", "warm", "one", "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" ] let emojiMap = { @@ -294,10 +294,11 @@ const ingredientMatch = /^(?:A )?([\-\/0-9 \u00BC-\u00BE\u2153-\u215E\u2009]*)\s function ingredientEl(string, terms) { if (string == "-") return m("hr"); - string = highlightTerms(string, terms) + // string = highlightTerms(string, terms) //console.log("terms", terms); let match = FRACTION_MAP.replace(clean(string)).match(ingredientMatch); + // let emoji = faviconForTitle(string); if (match) { return [m("span.quantity", match[1].replace(" ", "\u202F")), " ", m("span", {innerHTML:highlightTerms(match[2], terms)})] @@ -306,8 +307,11 @@ function ingredientEl(string, terms) { } function highlightTerms(string, terms) { - const pattern = new RegExp(`\\b(${Array.from(terms).join('|').replace("-","\\-") })\\b`, 'gi'); - return string.replace(pattern, match => `${match}`); + const pattern = new RegExp(`\\b((${Array.from(terms).join('|').replace("-","\\-")}))\\b`, 'gi'); + return string.replace(pattern, match => { + let emoji = undefined //faviconForTitle(match); + return `${ emoji ? emoji + " " : ""}${match}` + }); } function highlightTimes(string) { @@ -420,10 +424,11 @@ function render() { let ingredients = json.recipeIngredient; var ingredientTerms = new Set( - Array.from(ingredients.join("\n").matchAll(/[A-Za-z\-]+/g)).map(m => m[0].length > 2 ? m[0].toLowerCase(): "") + Array.from(ingredients.join("\n").matchAll(/(\p{L}\p{M}?)+/gu)).map(m => m[0].length > 2 ? m[0].toLowerCase(): "") ); if (typeof instructions === "string") instructions = [instructions] instructions = flattenInstructions(instructions) + console.log("instructions", instructions) let intructionTerms = new Set( Array.from(instructions.flat().join("\n").matchAll(/[A-Za-z\-]+/g)).map(m => m[0].length > 2 ? m[0].toLowerCase(): "") ); @@ -443,12 +448,14 @@ function render() { ingredients = ingredients.map(i => m("div.ingredient", { onclick: markIngredient }, ingredientEl(clean(i), ingredientTerms))); let step = 1; - function renderInstructions(instruction, terms) { + function renderInstructions(instruction, terms, i) { if (Array.isArray(instruction)) { - let instructions = instruction.map(i => renderInstructions(i, terms)); + let instructions = instruction.map((inst,i) => renderInstructions(inst, terms, i)); let className = "step"; - if (instructions[0].tagName=="H3") className = "step header" - return m("ul", {className}, instructions); + + if (instructions[0].tagName=="H3") className = "step header"; + let number = undefined // m("div.number", {}, "" + step++) + return m("ul", {className}, number, instructions); } let text = (instruction?.text || instruction); @@ -458,7 +465,9 @@ function render() { if (text?.endsWith(":")) return m("h3", text); return m("li", { onclick: highlightStep }, - m("span.number" + (step > 9 ? ".big" : ""), `${step++}`), + i == 0 ? + m("span.bullet.number" + (step > 9 ? ".big" : ""), (step++).toString()) : + m("span.bullet.substep", "ยท"), m("span.substep",{innerHTML:highlightTimes(highlightTerms(FRACTION_MAP.replace(text.trim()), terms))})) } @@ -489,26 +498,25 @@ function render() { } if (end) { let string = text.substring(start, end); - steps.push(string); + steps.push(string.trim()); start = end; } break; case "\n": - steps.push(text.substring(start, i)); + steps.push(text.substring(start, i).trim()); start = i; default: } } steps.push (text.substring(start)); - console.log("step", steps) return steps; } function flattenInstructions(instruction) { if (instruction.itemListElement) { - return ["= " + instruction.name].concat(flattenInstructions(instruction.itemListElement).flat()); + return ["= " + instruction.name].concat(flattenInstructions(instruction.itemListElement)); } if (Array.isArray(instruction)) { @@ -528,8 +536,9 @@ function render() { return text; } - instructions = instructions.map(i => renderInstructions(i, ingredientTerms)); + if (instructions.length == 1) console.log("One instruction") + instructions = instructions.map(inst => renderInstructions(inst, ingredientTerms)); // instructions = instructions.map(i => m("div.step", { onclick: highlightStep }, i)) @@ -554,6 +563,7 @@ function render() { var bgImg = new Image(); bgImg.onload = function(){ let thumbnail = document.querySelector("#thumbnail"); + if (!thumbnail) return; let thumbnailContainer = document.querySelector("#thumbnail-container"); thumbnail.style.backgroundImage = 'url("' + bgImg.src + '")'; @@ -573,15 +583,19 @@ function render() { console.log("image", bgImg.src) + let originalURL = json.mainEntityOfPage?.["@id"] ?? ((json.mainEntityOfPage == true) ? false : json.mainEntityOfPage) ?? json.url; + console.log({originalURL}) + let hostname = originalURL ? new URL(originalURL).hostname.replace("www.","") : "" let qrImage = QRCodeURL(params.originalURL, {margin:0}); - let originalURL = json.mainEntityOfPage?.["@id"] || json.mainEntityOfPage || json.url; + let publisherImage = json.publisher?.image ?.[0]?.url ?? json.publisher ?.logo ?.url; + document.body.appendChild( m(".recipe", {}, image ? m("#thumbnail-container", m("#thumbnail.thumbnail.print-hide", { style: "background-image:url(" + image + ");" })) : null, m(".recipe-content", m("header", m("a.publisherlink", {href:originalURL, target:"_blank"}, - m("img.publisher", { src: json.publisher?.image ?.[0]?.url ?? json.publisher ?.logo ?.url }), + publisherImage ? m("img.publisher", { src: publisherImage }) : hostname, ), m(".headerflex", m(".headerleft", @@ -630,7 +644,7 @@ function render() { qrImage ? m("img#qr.qr.print-show", {src:qrImage}) : null, // m("canvas#qr") ), - m(reformat ? "section.instructions.numbered" : "section.instructions", + m(reformat ? "section.instructions.numbered" : "section.instructions.numbered", m("caption.ingredients-title", {onclick:() => {reformat = !reformat; render(); return false;}}, reformat ? "Steps" : "Instructions", m("div.listtoggle.print-hide", {innerHTML: ''}), @@ -671,7 +685,7 @@ function keepAwake() { var path = window.script.substring(0, window.script.lastIndexOf(".")); var cssURL = path + ".css"; loadScript(path + '/../../js/qrious.min.js', null, "").then(() => { - console.log("qrious loaded"); + console.log("qrious loaded", params.originalURL.length); var qr = new QRious({ element: document.getElementById("qr"), background: 'transparent', diff --git a/package.json b/package.json index 0b79118..130fec3 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,8 @@ { "dependencies": { "brotli-wasm": "^1.1.0" + }, + "devDependencies": { + "netlify-cli": "^11.5.1" } }