diff --git a/docs/render.js b/docs/render.js index 9afc11d..dcac286 100644 --- a/docs/render.js +++ b/docs/render.js @@ -79,6 +79,7 @@ window.addEventListener("message", function(e) { }, false); function QRCodeURL(url, options) { + if (url.length > 2953) return undefined; let size = options?.size ?? 547; let errorCorrection = options?.correction ?? 'L'; let margin = options?.margin?.toString() || "1"; diff --git a/docs/render/recipe.css b/docs/render/recipe.css index eff3ca7..16370a6 100644 --- a/docs/render/recipe.css +++ b/docs/render/recipe.css @@ -14,8 +14,11 @@ body { rgba(16, 12, 12, 0.54); --hover-background-color: rgba(16, 12, 12, 0.07); - --image-placeholder-color: var(--hover-background-color); + --image-placeholder-color: + var(--hover-background-color); --background-color:white; +--alt-text-color: + rgb(0, 118, 173); } .thumbnail { @@ -39,18 +42,62 @@ body { /* text-decoration:underline; */ } -.time-button { - /* background-color:cyan; */ - white-space: nowrap; - border-radius:1em; - /* padding:0 0.3em; */ - /* border:1px solid currentColor; */ - text-decoration:underline; - text-decoration-style: dotted; + +@media screen { + .timer { + /* background-color:cyan; */ + white-space: nowrap; + border-radius: 2px; + padding: 0 0.1em; + margin: 0 -0.1em; + /* text-decoration:underline; */ + text-decoration-style: dotted; + box-sizing: border-box; + /* color: var(--background-color); */ + + border-bottom:1px solid transparent; + border-bottom-color: currentColor; + /* background-color: var(--text-color); */ + } + + .timer:hover, + .timer.active { + color: var(--background-color); + background-color:var(--text-color); + z-index:100; + /* border:1px solid currentColor; */ + position:relative; + + border-bottom-color: transparent; + } + + .timer.active .label{ + opacity:0.0; + } + .timer .countdown{ + position: + absolute; + display: + none; + width: 100%; + text-align: center; + left: 0; + } + .timer.active .countdown { + display:inline-block; + } +} + + +@media print { + .timer { + font-weight:800; + } } img.publisher { max-width: 5em; + max-height: 2em; /* margin-top: -0.1em; */ /* transform:scale(0.7); */ /* float: @@ -65,11 +112,11 @@ img.publisher { .columns { display: flex; - gap: 2em; + gap: 3em; margin-bottom: 4em; align-content: center; justify-content: center; - flex-direction: row; + flex-direction: row-reverse; margin-top: 1em; } @@ -136,15 +183,29 @@ span.number { /* background: black; */ } -.complete span.number, -li:hover span.number { - 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; +@media screen { + .complete span.number, li:hover span.number { + 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; -} -.complete span.number { - border-color:transparent; + } + .complete span.number { + border-color:transparent; + } + + .complete .noun { + color:var(--disabled-text-color); + } + .ingredient.complete, + li.complete { + text-decoration: line-through; + color:var(--disabled-text-color); + + text-decoration: line-through; + /*noinspection CssOverwrittenProperties*/ + text-decoration: rgba(0,0,0,0.75) solid 0.5px line-through; /* Ignored in CSS1/CSS2 UAs */ + } } .number.big { @@ -212,22 +273,20 @@ li:hover { .ingredients { font-weight: 600; font-size: 88%; - /* position: - sticky; */ + position: sticky; /* top: 0px; */ /* padding-top: 1.5em; */ flex: 0 1 35%; /* color: rgba(0,0,0,0.8); */ - /* color:rgb(0, 118, 173); */ + /* color: + rgb(0, 118, 173); */ } .noun { font-weight: 700; - color: var(--opaque-text-color); -} -.complete .noun { - color:var(--disabled-text-color); + color: var(--alt-text-color); } + li:hover .noun { text-decoration: 1px dotted underline; /* background-color: var(--hover-background-color); */ @@ -274,16 +333,6 @@ li:hover .noun { right; } -.ingredient.complete, -li.complete { - text-decoration: line-through; - color:var(--disabled-text-color); - -text-decoration: line-through; -/*noinspection CssOverwrittenProperties*/ -text-decoration: rgba(0,0,0,0.75) solid 0.5px line-through; /* Ignored in CSS1/CSS2 UAs */ -} - .ingredient:hover { opacity: 1.0; background-color: rgba(128, 128, 128, 0.1) @@ -291,7 +340,7 @@ text-decoration: rgba(0,0,0,0.75) solid 0.5px line-through; /* Ignored in CSS1/C .step { max-width: 80em; - cursor: pointer; + cursor: default; /* border:1px solid red; */ margin-bottom: 1em; } @@ -422,6 +471,7 @@ a.action .icon { user-select: none; /* float: left; */ display: inline-block; + -webkit-print-color-adjust: exact; } @@ -431,7 +481,7 @@ a.action .icon { min-height:100vh; } .print-hide { - display: none; + display: none !important; } @page { @@ -544,7 +594,7 @@ img.qr { width: 16px; height: 16px; } -.icon.timer { +.icon.time { background-position: -72px -16px; width: 16px; height: 16px; @@ -647,4 +697,12 @@ a.publisherlink { /* order: -2; */ /* width: 100%; */ float: left; +} + +.headerleft h1 { + max-width: 16em; +} + +span.timer.active.expired { + background-color: rgb(221, 40, 0); } \ No newline at end of file diff --git a/docs/render/recipe.js b/docs/render/recipe.js index 0111a23..30319fa 100644 --- a/docs/render/recipe.js +++ b/docs/render/recipe.js @@ -265,6 +265,7 @@ function highlightStep(e) { // el.classList.toggle("complete", ) // } if (e.target.classList.contains("noun")) return; + if (e.target.closest(".timer")) return; if (e.target.tagName == "A") return; e.target.closest("li").classList.toggle("complete") @@ -293,12 +294,67 @@ function highlightTerms(string, terms) { } function highlightTimes(string) { - const pattern = /([0-9]+) ?(minutes|hours)/g; - return string.replace(pattern, match => { - return `${match}` + const pattern = /([0-9]+)\s*(?:to|-|–)?\s*([0-9]+)?\s?(minutes?|hours?)/g; + return string.replace(pattern, (match, t1, t2, tt, offset, groups) => { + // console.log("match", t1, t2, tt) + + return `${match}` }); } +function dismissTimer(e) { + console.log(e.target); +} + +function playSound(loc) { + var audio = new Audio(loc); + // audio.play(); +} + +function startTimer(e, t1, t2) { + if (!e.startTime) { + e.startTime = new Date(); + let duration = t1 * 60 + e.endTime = new Date(e.startTime.getTime() + duration * 1000); + + console.log("end", e.endTime); + let countdownEl = e.querySelector(".countdown"); + + console.log(e, t1, t2); + e.classList.add("active") + let update = () => { + let now = new Date(); + let percent = (now - e.startTime) / 1000 / duration * 100; + // console.log("perc", (now - e.startTime)/1000 , percent, duration) + e.style.backgroundImage = `linear-gradient(to right, transparent ${percent}%, rgba(255,255,255,0.4) ${percent}%)` + let remaining = Math.round((e.endTime - now)/1000); + let expired = percent > 100; + if (expired) { + playSound("recipe/beep.mp4") + delete e.style.backgroundImage; + // clearInterval(e.interval); + remaining = -remaining; + e.classList.add("expired") + } + + let countdownString = `${expired ? "-": ""}${Math.floor(remaining / 60)}m ${remaining % 60}s` + countdownEl.innerText = countdownString; + + } + e.interval = setInterval(update, 1000) + update(); + } else { + e.interval = clearInterval(e.interval) + e.classList.remove("active"); + e.classList.remove("expired"); + + e.style.backgroundImage = ''; + delete e.startTime; + } +} + +window.startTimer = startTimer; + function share() { parent.postMessage({share:{}}, "*"); } @@ -342,7 +398,6 @@ function render() { let favicon = faviconForTitle(title) || "🍴"; parent.postMessage({title:title, favicon:favicon, image:image, description:description, wakeLock:true, updateURL:true}, "*"); description = description.split("\n").join("

") - console.log(description) // let text = instructions.join(" "); let ingredients = json.recipeIngredient; @@ -386,6 +441,24 @@ function render() { m("span.substep",{innerHTML:highlightTimes(highlightTerms(FRACTION_MAP.replace(text.trim()), terms))})) } + function stepsFromText(text) { + let steps = [] + let components = text.split(/(?<=[\.\!\?]|[\.\!\?]\)|\))\s+(?!\()/); + + let append = false; + while (components.length) { + let step = components.shift() + if (append) { + steps.push(steps.pop() + " " + step) + } else { + steps.push(step); + } + if (step.indexOf("(") >= 0) { append = true; } + if (step.includes(")")) { append = false; } + } + + return steps; + } function flattenInstructions(instruction) { if (instruction.itemListElement) { return ["= " + instruction.name].concat(flattenInstructions(instruction.itemListElement).flat()); @@ -400,7 +473,7 @@ function render() { console.log() if (reformat) { - text = text.replace(/(\.\)? )+/g,"$1\n").split("\n")///text.match( /[0-9\. ]{0,6}[^\.!\?]+[\.!\?]+/g ); + text = stepsFromText(text); } else { text = text.match( /[^\n]+/g ); } @@ -436,13 +509,16 @@ function render() { bgImg.onload = function(){ let thumbnail = document.querySelector("#thumbnail"); let thumbnailContainer = document.querySelector("#thumbnail-container"); - thumbnail.style.backgroundImage = 'url(' + bgImg.src + ')'; + thumbnail.style.backgroundImage = 'url("' + bgImg.src + '")'; thumbnail.style.filter = `blur(${thumbnailContainer.offsetHeight / bgImg.naturalHeight * 1}px)`; thumbnail.style.transform = `scale(1.1)`; setTimeout(() => thumbnail.style.opacity = 1.0, 100); }; bgImg.src = image; + console.log("image", bgImg.src) + + let qrImage = QRCodeURL(params.originalURL, {margin:0}); let originalURL = json.mainEntityOfPage?.["@id"] || json.mainEntityOfPage || json.url; document.body.appendChild( m(".recipe", {}, @@ -458,7 +534,7 @@ function render() { m(".metadata", (recipeYield) ? m("div", m("span.yield", m(".icon.servings"), recipeYield)) : null, json.totalTime ? m(".time", - m(".icon.timer"), + m(".icon.time"), json.totalTime ? m("span", formatTime(json.totalTime)) : undefined, // " (", @@ -496,7 +572,7 @@ function render() { m("section.ingredients", m("caption.ingredients-title", "Ingredients"), ingredients, - m("img.qr.print-show", {src:QRCodeURL(params.originalURL, {margin:0})}) + qrImage ? m("img.qr.print-show", {src:qrImage}) : null ), m(reformat ? "section.instructions.numbered" : "section.instructions", m("caption.ingredients-title", {onclick:() => {reformat = !reformat; render(); return false;}}, diff --git a/docs/render/recipe/beep.mp4 b/docs/render/recipe/beep.mp4 new file mode 100644 index 0000000..48c1ec4 Binary files /dev/null and b/docs/render/recipe/beep.mp4 differ diff --git a/docs/render/recipe/beep2.mp4 b/docs/render/recipe/beep2.mp4 new file mode 100644 index 0000000..394f010 Binary files /dev/null and b/docs/render/recipe/beep2.mp4 differ