") // let text = instructions.join(" "); let ingredients = json.recipeIngredient || json.ingredients; var ingredientTerms = new Set( 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) let intructionTerms = new Set( Array.from(instructions.flat().join("\n").matchAll(/[A-Za-z\-]+/g)).map(m => m[0].length > 2 ? m[0].toLowerCase(): "") ); ignoredTerms.forEach((t) => { ingredientTerms.delete(t) }) ingredientTerms.forEach((t) => { if (!intructionTerms.has(t)) { ingredientTerms.delete(t); } }) ingredientTerms.delete(""); //console.log(ingredientTerms); // console.log("1 tablespoon".replace(/(tablespoon)/, (a) => { // return replacements[a]; // })) ingredients = ingredients.map(i => m("div.ingredient", { onclick: markIngredient }, ingredientEl(clean(i), ingredientTerms))); let step = 1; function renderInstructions(instruction, terms, i) { if (Array.isArray(instruction)) { let instructions = instruction.map((inst,i) => renderInstructions(inst, terms, i)); let className = "step"; 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); if (!text) return; if (text?.startsWith("= ")) return m("h3", text.substring(2)); if (text?.endsWith(":")) return m("h3", text); return m("li", { onclick: highlightStep }, 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))})) } function stepsFromText(text) { let abbreviations = ["tsp", "tbsp", "oz", "lb", "lbs"]; let steps = []; let i = 0; let start = 0; let depth = 0; let lastSpace = 0; for (let i = 0; i < text.length; i++) { switch (text[i]) { case "(": depth++; break; case ")": depth--; break; case " ": lastSpace = i; break; case ".": case "!": case "?": let lastWord = text.substring(lastSpace, i).trim(); lastWord = lastWord.match(/\w+$/)?.pop().toLowerCase(); if (abbreviations.includes(lastWord)) { continue; } let end; if (text[i+1] == ")" && text[i+2] == " ") { end = i + 2; } else if (text[i+1] == " " && text[i+2] != "(" && depth == 0) { end = i + 1; } if (end) { let string = text.substring(start, end); steps.push(string.trim()); start = end; } break; case "\n": steps.push(text.substring(start, i).trim()); start = i; default: } } steps.push (text.substring(start)); return steps; } function flattenInstructions(instruction) { if (instruction.itemListElement) { return ["= " + instruction.name].concat(flattenInstructions(instruction.itemListElement)); } if (Array.isArray(instruction)) { if (Array.isArray(instruction[0])) instruction = instruction.flat(); return instruction.map(i => flattenInstructions(i)); } let text = (instruction.text || instruction.name || instruction); if (reformat) { text = stepsFromText(text); } else { text = text.match( /[^\n]+/g ); } // text = [text] // if (true) text = text.replace(/\. /g, ".
") return text; } 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)) let rating = json.aggregateRating; let ratingCount = rating?.ratingCount; if (ratingCount && ratingCount < 10) rating = undefined; let recipeYield = (getStringProperty(json.recipeYield)); if (!isNaN(parseInt(recipeYield?.charAt(recipeYield?.length - 1)))) recipeYield += " servings"; function imgload(e) { console.log(e, "img"); var image = document.querySelector('img'); var isLoaded = image.complete && image.naturalHeight !== 0; alert(isLoaded); } 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 + '")'; let blur = 1; blur = Math.max(blur, Math.max(thumbnailContainer.offsetHeight * window.devicePixelRatio / bgImg.naturalHeight, thumbnailContainer.offsetWidth * window.devicePixelRatio / bgImg.naturalWidth)) if (blur > 1) thumbnail.style.filter = `blur(${blur}px)`; setTimeout(() => thumbnail.style.opacity = 1.0, 0); // if (window.scrollY == 0) setTimeout(() => { // const yOffset = -20; // const element = document.querySelector('.recipe-content'); // const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset; // window.scrollTo({top: y, behavior: 'smooth'}); // }, 1000) }; bgImg.src = image; let originalURL = json.mainEntityOfPage?.["@id"] ?? ((json.mainEntityOfPage == true) ? false : json.mainEntityOfPage) ?? json.url; let hostname = originalURL ? new URL(originalURL).hostname.replace("www.","") : "" let qrImage = undefined // QRCodeURL(params.originalURL, {margin:0}); 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", { onclick:(e) => document.body.classList.toggle('noblur'), style: "background-image:url(" + image + ");" })) : null, m(".recipe-content", m("div.headercolumns", m("header", m("a.publisherlink", {href:originalURL, target:"_blank"}, publisherImage ? m("img.publisher", { src: publisherImage }) : (json.publisher?.name || hostname), ), m(".headerflex", m(".headerleft", m("h1", {onclick:keepAwake}, title), m(".metadata", (recipeYield) ? m("div", m("span.yield", m(".icon.servings", {innerHTML:icons.servings}), recipeYield)) : null, json.totalTime ? m(".time", m(".icon.time", {innerHTML:icons.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, // ")" ) : null, (rating) ? m("div.rating", m(".icon.rating", {innerHTML:icons.rating}), parseFloat(rating.ratingValue).toFixed(1), " ", // ratingCount ? m("span.count", ratingCount.toString()) : null ) : null, json.nutrition?.calories ? m("div", m(".icon.info", {innerHTML:icons.info}), (json.nutrition?.calories.toString().replace("calories", "Cal").replace("kcal", "Cal")) + (isNaN(json.nutrition?.calories) ? '' : ' Cal')) : null, // m("div.spacer"), ), ), m(".actions.print-hide.watch-hide", // originalURL ? m("a.action", { title:"Open original", href: originalURL, target:"_blank"}, m(".icon.public", {innerHTML:icons.public})) : null, m("a.action", { title:"Share", onclick: share}, m(".icon.share", {innerHTML:icons.share})), // m("a.action", { title:"Show steps as list", onclick: () => {reformat = !reformat; render(); return false;}}, m(".icon.checklist")), m("a.action", { title:"Print", onclick: () => {window.print(); return false;} }, m(".icon.print", {innerHTML:icons.print})), ) ), description ? m(".description", {innerHTML:description}, json.author?.name ? m("span.author", (" โโ " + json.author?.name)) : null, m("p"), ) : null, ), ), m(".columns", m(isWatch ? "section.ingredients" : "section.ingredients.hanging", m("caption.ingredients-title", {onclick:(e) => {e.target.closest("section").classList.toggle("hanging")}},"Ingredients"), ingredients, // m("canvas#qr") ), 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: ''}), ), instructions) ), ), qrImage ? m("img#qr.qr.print-show", {src:qrImage, onclick:() => window.print()}) : null, ) ) } function keepAwake() { let ctx = new AudioContext(); let bufferSize = 2 * ctx.sampleRate, emptyBuffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate), output = emptyBuffer.getChannelData(0); for(let i = 0; i < bufferSize; i++) output[i] = 0; let source = ctx.createBufferSource(); source.buffer = emptyBuffer; source.loop = true; let node = ctx.createMediaStreamDestination(); source.connect(node); let audio = document.createElement("audio"); audio.style.display = "none"; document.body.appendChild(audio); audio.srcObject = node.stream; audio.play(); console.log("playing", audio) } // 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", params.originalURL.length); // var qr = new QRious({ // element: document.getElementById("qr"), // background: 'transparent', // foreground: 'currentColor', // size: 512, // value: params.originalUrl.substring(0), // }); // }) // loadSyle(cssURL).then(render);