Update Recipe formatting

This commit is contained in:
Nicholas Jitkoff 2022-09-05 11:41:33 -07:00
parent 64d15d9049
commit df2bd0c682
5 changed files with 157 additions and 47 deletions

31
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,31 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "netlify dev",
"type": "node",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"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": ["<node_internals>/**"],
"outFiles": ["${workspaceFolder}/.netlify/functions-serve/**/*.js"],
"program": "${workspaceFolder}/node_modules/.bin/netlify",
"args": ["functions:serve"],
"console": "integratedTerminal"
}
]
}

9
.vscode/settings.json vendored Normal file
View file

@ -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"
}

View file

@ -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);
}

View file

@ -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 => `<span class="noun" id="term-${match}">${match}</span>`);
const pattern = new RegExp(`\\b((${Array.from(terms).join('|').replace("-","\\-")}))\\b`, 'gi');
return string.replace(pattern, match => {
let emoji = undefined //faviconForTitle(match);
return `<span class="noun" id="term-${match.trim().replace(" ","-")}">${ emoji ? emoji + "&nbsp;" : ""}${match}</span>`
});
}
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: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"><mask id="a" width="16" height="16" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#D9D9D9" d="M0 0h16v16H0z"/></mask><g mask="url(#a)"><path fill="#000" d="M3.7 12.667 1.333 10.3l.934-.933 1.416 1.416L6.517 7.95l.933.95-3.75 3.767Zm0-5.334L1.333 4.967l.934-.934L3.683 5.45l2.834-2.833.933.95L3.7 7.333Zm4.967 4V10h6v1.333h-6Zm0-5.333V4.667h6V6h-6Z"/></g></svg>'}),
@ -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',

View file

@ -1,5 +1,8 @@
{
"dependencies": {
"brotli-wasm": "^1.1.0"
},
"devDependencies": {
"netlify-cli": "^11.5.1"
}
}