mirror of
https://github.com/arfct/itty-bitty.git
synced 2026-03-11 08:54:33 +00:00
Add extractor
This commit is contained in:
parent
6fb057cec6
commit
82ad60c7bb
6 changed files with 275 additions and 128 deletions
29
docs/extractrecipe.js
Normal file
29
docs/extractrecipe.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
javascript:((itty_bitty_recipes) => {
|
||||||
|
let l=document.querySelector('script[type="application/ld+json"]');
|
||||||
|
if (!l) return alert("No Recipe element found in HTML")
|
||||||
|
let ldjson = l.innerText.trim();
|
||||||
|
let json = JSON.parse(ldjson);
|
||||||
|
if (Array.isArray(json)) json = json.find((o)=>o['@type']=="Recipe")
|
||||||
|
console.log(json)
|
||||||
|
var blob = new Blob([JSON.stringify(json)],{type : 'application/ld+json;charset=utf-8'});
|
||||||
|
var a = new FileReader();
|
||||||
|
a.onload = function(e) {
|
||||||
|
let url = itty_bitty_recipes + '/#/' + e.target.result;
|
||||||
|
console.log("URL", url.length, url)
|
||||||
|
open(url);
|
||||||
|
};
|
||||||
|
a.readAsDataURL(blob);
|
||||||
|
})('http://localhost')
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
javascript:(function(){
|
||||||
|
let id="ittybitty";
|
||||||
|
if (document.getElementById(id)) return;
|
||||||
|
var l = document.createElement('script');
|
||||||
|
l.setAttribute('type','text/javascript');
|
||||||
|
l.setAttribute('src','http://localhost/extractrecipe.js');
|
||||||
|
l.id=id;
|
||||||
|
document.head.appendChild(l);
|
||||||
|
})();
|
||||||
|
*/
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
<link id="favicon" rel=icon href=''>
|
<link id="favicon" rel=icon href=''>
|
||||||
<script src="/js/lzma/lzma-d-min.js"></script>
|
<script src="/js/lzma/lzma-d-min.js"></script>
|
||||||
|
<script src="js/lzma/lzma_worker-min.js"></script>
|
||||||
|
|
||||||
<script src="/data.js"></script>
|
<script src="/data.js"></script>
|
||||||
<script src="/index.js"></script>
|
<script src="/index.js"></script>
|
||||||
|
|
||||||
|
|
|
||||||
217
docs/index.js
217
docs/index.js
|
|
@ -1,109 +1,133 @@
|
||||||
var HEAD_TAGS = "PGJhc2UgdGFyZ2V0PSJfdG9wIj4K";
|
(function () {
|
||||||
var HEAD_TAGS_EXTENDED =
|
|
||||||
"PG1ldGEgY2hhcnNldD0idXRmLTgiPjxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgiPjxiYXNlIHRhcmdldD0iX3RvcCI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj5ib2R5e21hcmdpbjowIGF1dG87cGFkZGluZzoxMnZtaW4gMTB2bWluO21heC13aWR0aDozNWVtO2xpbmUtaGVpZ2h0OjEuNWVtO2ZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLEJsaW5rTWFjU3lzdGVtRm9udCxzYW5zLXNlcmlmO3dvcmQtd3JhcDogYnJlYWstd29yZDt9PC9zdHlsZT4g";
|
|
||||||
|
|
||||||
let dataRE = /^data:(?<type>[^;,]+)?(?<attrs>(?:;[^;,]+=[^;,]+)*)?(?:;(?<encoding>\w+64))?,(?<data>.*)$/
|
const HEAD_TAGS = "PGJhc2UgdGFyZ2V0PSJfdG9wIj4K";
|
||||||
|
const HEAD_TAGS_EXTENDED = "PG1ldGEgY2hhcnNldD0idXRmLTgiPjxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgiPjxiYXNlIHRhcmdldD0iX3RvcCI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj5ib2R5e21hcmdpbjowIGF1dG87cGFkZGluZzoxMnZtaW4gMTB2bWluO21heC13aWR0aDozNWVtO2xpbmUtaGVpZ2h0OjEuNWVtO2ZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLEJsaW5rTWFjU3lzdGVtRm9udCxzYW5zLXNlcmlmO3dvcmQtd3JhcDogYnJlYWstd29yZDt9PC9zdHlsZT4g";
|
||||||
|
|
||||||
function dismiss() {
|
let dataUrlRE = /^data:(?<type>[^;,]+)?(?<attrs>(?:;[^;,]+=[^;,]+)*)?(?:;(?<encoding>\w+64))?,(?<data>.*)$/
|
||||||
if (document.getElementById("never").checked) window.localStorage.setItem('toasted', true);
|
|
||||||
document.body.classList.remove("toasting")
|
|
||||||
}
|
|
||||||
|
|
||||||
function setFavicon(favicon) {
|
function dismiss() {
|
||||||
document.getElementById("favicon").href = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"><text y=".9em">'+ favicon + '</text></svg>'
|
if (document.getElementById("never").checked) window.localStorage.setItem('toasted', true);
|
||||||
}
|
document.body.classList.remove("toasting")
|
||||||
|
|
||||||
var validTypes = ["image/svg+xml", "application/ld+json"]
|
|
||||||
window.addEventListener("message", (e) => {
|
|
||||||
if (e.origin == 'null') {
|
|
||||||
if (e.data.title) document.title = e.data.title;
|
|
||||||
if (e.data.favicon) setFavicon(e.data.favicon);
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
window.onhashchange = window.onload = function() {
|
|
||||||
var hash = window.location.hash.substring(1);
|
|
||||||
if (window.location.search) {
|
|
||||||
console.log("window.location.search.substring(1)", window.location.search.substring(1))
|
|
||||||
window.history.replaceState(null, null, window.location.search.substring(1) + "#" + hash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hash.length < 3) {
|
function setFavicon(favicon) {
|
||||||
location.href = "/edit";
|
document.getElementById("favicon").href = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"><text y=".9em">'+ favicon + '</text></svg>'
|
||||||
} else {
|
}
|
||||||
// if (!window.localStorage.getItem('toasted')) document.body.classList.add("toasting");
|
|
||||||
|
|
||||||
var iframe = document.getElementById("iframe");
|
const renderers = {"application/ld+json": "recipe"}
|
||||||
var link = document.getElementById("edit");
|
|
||||||
var preamble = undefined;
|
|
||||||
var renderMode = "data";
|
|
||||||
|
|
||||||
var slashIndex = hash.indexOf("/");
|
window.addEventListener("message", function(e) {
|
||||||
var title = hash.substring(0, slashIndex);
|
console.log("Message:", e.origin, e.data)
|
||||||
document.title = title.length
|
// if (e.origin == 'null' || e.origin = ) {
|
||||||
? decodeURIComponent(title.replace(/_/g, " "))
|
if (e.data.title) document.title = e.data.title;
|
||||||
: location.hostname;
|
if (e.data.favicon) setFavicon(e.data.favicon);
|
||||||
|
if (e.data.setStorage) document.localStorage.setItem(contentHash, e.data.set);
|
||||||
|
if (e.data.getStorage) document.getElementById("iframe").postMessage(document.localStorage.getItem(contentHash), e.origin)
|
||||||
|
// }
|
||||||
|
}, false);
|
||||||
|
|
||||||
hash = hash.substring(slashIndex + 1);
|
// window.onhashchange =
|
||||||
var editable = hash.charAt(0) == "?";
|
window.onload = function() {
|
||||||
if (editable) {
|
if (window.location.search) { // Redirect search to path (coming out of server opengraph forwarding)
|
||||||
hash = hash.substring(1);
|
console.log("window.location.search.substring(1)", window.location.search.substring(1))
|
||||||
|
window.history.replaceState(null, null, window.location.search.substring(1) + "#" + fragment)
|
||||||
}
|
}
|
||||||
if (hash.indexOf("data:") != 0) {
|
|
||||||
var colon = hash.indexOf(":");
|
|
||||||
if ( colon > 0 && colon < 15) {
|
|
||||||
document.body.classList.remove("toasting");
|
|
||||||
return window.location.replace(hash);
|
|
||||||
}
|
|
||||||
var compressed = true;
|
|
||||||
preamble = HEAD_TAGS_EXTENDED;
|
|
||||||
hash =
|
|
||||||
"data:text/html;charset=utf-8;" +
|
|
||||||
(compressed ? "bxze64," : "base64,") +
|
|
||||||
hash;
|
|
||||||
} else if (hash.indexOf("data:text/html;") == 0) {
|
|
||||||
preamble = HEAD_TAGS;
|
|
||||||
} else if (hash.indexOf("data:text/plain;") == 0) {
|
|
||||||
preamble = HEAD_TAGS_EXTENDED;
|
|
||||||
} else if (hash.indexOf("data:text/") == 0) {
|
|
||||||
} else if (hash.indexOf("data:image/") == 0) {
|
|
||||||
} else {
|
|
||||||
let match = hash.match(dataRE);
|
|
||||||
let type = match?.groups.type;
|
|
||||||
if (type == "application/ld+json") {
|
|
||||||
let script = '<script src="' + location.origin + '/render/recipe.js"></script>'
|
|
||||||
script = script + " ".repeat(3 - (script.length % 3))
|
|
||||||
preamble = btoa(script);
|
|
||||||
renderMode = "frame";
|
|
||||||
} else if (!validTypes.includes(type)) {
|
|
||||||
console.log("unknown type, rendering as download")
|
|
||||||
let extension = title.split(".")
|
|
||||||
document.querySelector("#dl-name").innerText = title;
|
|
||||||
if (extension.length > 1) document.querySelector("#dl-image").innerText = extension.pop();
|
|
||||||
renderMode = "download";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
link.onclick = function() {
|
|
||||||
location.href = "/edit" + location.hash;
|
|
||||||
};
|
|
||||||
|
|
||||||
var isIE = navigator.userAgent.match(/rv:11/);
|
var isIE = navigator.userAgent.match(/rv:11/);
|
||||||
var isEdge = navigator.userAgent.match(/Edge\//);
|
var isEdge = navigator.userAgent.match(/Edge\//);
|
||||||
var isWatch = (window.outerWidth < 200);
|
var isWatch = (window.outerWidth < 200);
|
||||||
|
|
||||||
|
var fragment = window.location.hash.substring(1);
|
||||||
|
if (fragment.length < 3) {
|
||||||
|
return location.href = "/edit";
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (!window.localStorage.getItem('toasted')) document.body.classList.add("toasting");
|
||||||
|
|
||||||
|
var iframe = document.getElementById("iframe");
|
||||||
|
var link = document.getElementById("edit");
|
||||||
|
var dataPrefix = undefined;
|
||||||
|
var renderMode = "data";
|
||||||
|
|
||||||
|
var slashIndex = fragment.indexOf("/");
|
||||||
|
var title = fragment.substring(0, slashIndex);
|
||||||
|
document.title = title.length
|
||||||
|
? decodeURIComponent(title.replace(/_/g, " "))
|
||||||
|
: location.hostname;
|
||||||
|
|
||||||
|
fragment = fragment.substring(slashIndex + 1);
|
||||||
|
var editable = fragment.charAt(0) == "?";
|
||||||
|
if (editable) {
|
||||||
|
fragment = fragment.substring(1);
|
||||||
|
}
|
||||||
|
if (fragment.startsWith("data:")) {
|
||||||
|
let match = fragment.match(dataUrlRE);
|
||||||
|
let attrs = new URLSearchParams(match.groups.attrs?.substring(1).replace(/;/g, "&"));
|
||||||
|
|
||||||
|
if (fragment.indexOf("data:text/html;") == 0) {
|
||||||
|
dataPrefix = HEAD_TAGS;
|
||||||
|
} else if (fragment.indexOf("data:text/plain;") == 0) {
|
||||||
|
dataPrefix = HEAD_TAGS_EXTENDED;
|
||||||
|
renderMode = "frame";
|
||||||
|
} else if (fragment.indexOf("data:text/") == 0) {
|
||||||
|
} else if (fragment.indexOf("data:image/") == 0) {
|
||||||
|
} else {
|
||||||
|
let type = match?.groups.type;
|
||||||
|
const renderer = attrs.get("render") || renderers[type];
|
||||||
|
if (renderer) {
|
||||||
|
var script = renderer;
|
||||||
|
if (script.indexOf("/") == -1) script = location.origin + '/render/' + script + '.js'
|
||||||
|
renderMode = "script";
|
||||||
|
} else {
|
||||||
|
console.log("unknown type, rendering as download")
|
||||||
|
let extension = title.split(".")
|
||||||
|
document.querySelector("#dl-name").innerText = title;
|
||||||
|
if (extension.length > 1) document.querySelector("#dl-image").innerText = extension.pop();
|
||||||
|
renderMode = "download";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match?.groups.encoding == "base64") {
|
||||||
|
compressDataURI(fragment, function(compressedFragment) {
|
||||||
|
console.log("Compressing long url", fragment.length, compressedFragment.length)
|
||||||
|
window.location.hash = window.location.hash.replace(fragment, compressedFragment);
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var colon = fragment.indexOf(":");
|
||||||
|
if ( colon > 0 && colon < 15) {
|
||||||
|
document.body.classList.remove("toasting");
|
||||||
|
return window.location.replace(fragment);
|
||||||
|
}
|
||||||
|
var compressed = true;
|
||||||
|
dataPrefix = HEAD_TAGS_EXTENDED;
|
||||||
|
fragment =
|
||||||
|
"data:text/html;charset=utf-8;" +
|
||||||
|
(compressed ? "bxze64," : "base64,") +
|
||||||
|
fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
link.onclick = function() {
|
||||||
|
location.href = "/edit" + location.hash;
|
||||||
|
};
|
||||||
|
|
||||||
if ((isEdge || isIE) && location.href.length == 2083) {
|
if ((isEdge || isIE) && location.href.length == 2083) {
|
||||||
document.getElementById("warning").innerHTML =
|
document.getElementById("warning").innerHTML =
|
||||||
'Edge only supports shorter URLs (maximum 2083 bytes).<br>Larger sites may require a different browser.<br><a href="http://reference.bitty.site">Learn more</a>';
|
'Edge only supports shorter URLs (maximum 2083 bytes).<br>Larger sites may require a different browser.<br><a href="http://reference.bitty.site">Learn more</a>';
|
||||||
}
|
}
|
||||||
decompressDataURI(hash, preamble, function(dataURL) {
|
decompressDataURI(fragment, dataPrefix, function(dataURL, dataContent) {
|
||||||
if (!dataURL) return;
|
if (!dataURL) return;
|
||||||
iframe.sandbox = "allow-downloads allow-scripts allow-forms allow-top-navigation allow-popups allow-modals allow-popups-to-escape-sandbox";
|
iframe.sandbox = "allow-downloads allow-scripts allow-forms allow-top-navigation allow-popups allow-modals allow-popups-to-escape-sandbox";
|
||||||
|
|
||||||
|
if (renderMode == "script")
|
||||||
if (isIE && renderMode == "data") renderMode = "frame";
|
if (isIE && renderMode == "data") renderMode = "frame";
|
||||||
|
let contentTarget = iframe.contentWindow.document;
|
||||||
if (isWatch) {
|
if (isWatch) {
|
||||||
renderMode = "rewrite";
|
contentTarget = document;
|
||||||
}
|
}
|
||||||
console.log("Rendering via", renderMode)
|
console.log("Rendering via", renderMode)
|
||||||
dataURL = dataURL.replace("application/ld+json", "text/plain");
|
// dataURL = dataURL.replace("application/ld+json", "text/plain");
|
||||||
if (renderMode == "download") {
|
if (renderMode == "download") {
|
||||||
try {
|
try {
|
||||||
let dl = document.querySelector("#download");
|
let dl = document.querySelector("#download");
|
||||||
|
|
@ -116,25 +140,26 @@ window.onhashchange = window.onload = function() {
|
||||||
}
|
}
|
||||||
} else if (renderMode == "data") {
|
} else if (renderMode == "data") {
|
||||||
iframe.src = dataURL;
|
iframe.src = dataURL;
|
||||||
} else if (renderMode == "url") {
|
} else {
|
||||||
location.href = dataURL;
|
|
||||||
} else if (renderMode == "frame") {
|
|
||||||
dataToString(dataURL, function(content) {
|
dataToString(dataURL, function(content) {
|
||||||
var doc = iframe.contentWindow.document;
|
if (renderMode == "frame") {
|
||||||
doc.open();
|
writeDocContent(contentTarget, content)
|
||||||
doc.write(content);
|
} else if (renderMode == "script") {
|
||||||
doc.close();
|
writeDocContent(contentTarget, `<script>var data = ${content}</script><script src="${script}"></script>`)
|
||||||
});
|
}
|
||||||
} else if (renderMode == "rewrite") {
|
|
||||||
dataToString(dataURL, function(content) {
|
|
||||||
document.open();
|
|
||||||
document.write(content);
|
|
||||||
document.close();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var link = document.getElementById("edit");
|
var link = document.getElementById("edit");
|
||||||
link.href = "/edit" + location.hash;
|
link.href = "/edit" + location.hash;
|
||||||
link.style.display = editable ? "block" : "none";
|
link.style.display = editable ? "block" : "none";
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function writeDocContent(doc, content) {
|
||||||
|
doc.open();
|
||||||
|
doc.write(content);
|
||||||
|
doc.close();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
})()
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
* {
|
* {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
@ -12,12 +12,15 @@ body {
|
||||||
background-color: gray;
|
background-color: gray;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
margin-bottom:2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.publisher {
|
img.publisher {
|
||||||
max-width: 12em;
|
max-width: 5em;
|
||||||
float: right;
|
/* margin-top: -0.5em; */
|
||||||
margin-top: -0.5em;
|
/* transform:scale(0.7); */
|
||||||
|
|
||||||
|
float:right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recipe {
|
.recipe {
|
||||||
|
|
@ -37,10 +40,15 @@ img.publisher {
|
||||||
header {
|
header {
|
||||||
gap: 2em;
|
gap: 2em;
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
|
/*
|
||||||
|
padding-left:15em; */
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-top: 2em;
|
margin-top: 0.3em;
|
||||||
|
margin-bottom:0.3em;
|
||||||
|
line-height:125%;
|
||||||
|
font-weight:500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metadata {
|
.metadata {
|
||||||
|
|
@ -77,6 +85,21 @@ h1 {
|
||||||
line-height: 1.25em;
|
line-height: 1.25em;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
padding-left:3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ingredients .yield {
|
||||||
|
margin-bottom:2em;
|
||||||
|
font-weight:bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
margin-left:2em;
|
||||||
|
}
|
||||||
|
.ingredients .quantity {
|
||||||
|
float:left;
|
||||||
|
margin-left:-2.5em;
|
||||||
|
width:2em;
|
||||||
|
text-align:center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ingredient.complete,
|
.ingredient.complete,
|
||||||
|
|
@ -105,6 +128,13 @@ h1 {
|
||||||
background-color: currentColor;
|
background-color: currentColor;
|
||||||
opacity: 0.54;
|
opacity: 0.54;
|
||||||
}
|
}
|
||||||
|
hr {
|
||||||
|
/* width: 100px;
|
||||||
|
float: left;
|
||||||
|
height: 0.5px;
|
||||||
|
background-color: currentColor;
|
||||||
|
opacity: 0.54; */
|
||||||
|
}
|
||||||
|
|
||||||
a.action {
|
a.action {
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -112,14 +142,14 @@ a.action {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: currentColor;
|
color: currentColor;
|
||||||
|
opacity:0.4;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 1em;
|
|
||||||
line-height: 2em;
|
|
||||||
padding: 0 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.action:hover {
|
a.action:hover {
|
||||||
background-color: rgba(128, 128, 128, 0.1)
|
opacity:1.0;
|
||||||
|
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 480px) {
|
@media screen and (max-width: 480px) {
|
||||||
|
|
@ -134,6 +164,11 @@ a.action:hover {
|
||||||
}
|
}
|
||||||
.ingredient {
|
.ingredient {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
margin-left:2em;
|
||||||
|
margin-bottom:0.67em;
|
||||||
|
}
|
||||||
|
.ingredients .yield {
|
||||||
|
margin-left:3em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
let script = document.currentScript;
|
let script = document.currentScript;
|
||||||
|
|
||||||
|
console.log("data!", data);
|
||||||
|
|
||||||
|
|
||||||
FRACTION_MAP = {
|
FRACTION_MAP = {
|
||||||
'1/2': '\u00BD',
|
|
||||||
'1/4': '\u00BC',
|
'1/4': '\u00BC',
|
||||||
|
'1/2': '\u00BD',
|
||||||
'3/4': '\u00BE',
|
'3/4': '\u00BE',
|
||||||
'1/3': '\u2153',
|
'1/3': '\u2153',
|
||||||
'2/3': '\u2154',
|
'2/3': '\u2154',
|
||||||
|
|
@ -24,6 +26,16 @@ FRACTION_MAP = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStringProperty(stringOrArray, prop) {
|
||||||
|
if (Array.isArray(stringOrArray)) {
|
||||||
|
stringOrArray = stringOrArray.pop();
|
||||||
|
}
|
||||||
|
if (prop) {
|
||||||
|
stringOrArray = stringOrArray[prop]
|
||||||
|
}
|
||||||
|
return stringOrArray;
|
||||||
|
}
|
||||||
|
|
||||||
const m = (selector, ...args) => {
|
const m = (selector, ...args) => {
|
||||||
var attrs = (args[0] && typeof args[0] === 'object' && !Array.isArray(args[0]) && !(args[0] instanceof HTMLElement)) ? args.shift() : {};
|
var attrs = (args[0] && typeof args[0] === 'object' && !Array.isArray(args[0]) && !(args[0] instanceof HTMLElement)) ? args.shift() : {};
|
||||||
|
|
||||||
|
|
@ -57,12 +69,23 @@ const m = (selector, ...args) => {
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function clean(html) {
|
||||||
|
let doc = new DOMParser().parseFromString(html, 'text/html');
|
||||||
|
return doc.body.textContent || "";
|
||||||
|
}
|
||||||
|
|
||||||
function formatTime(time) {
|
function formatTime(time) {
|
||||||
const timeRE = /(?<sign>-)?P(?:(?<years>[.,\d]+)Y)?(?:(?<months>[.,\d]+)M)?(?:(?<weeks>[.,\d]+)W)?(?:(?<days>[.,\d]+)D)?T(?:(?<hours>[.,\d]+)H)?(?:(?<minutes>[.,\d]+)M)?(?:(?<seconds>[.,\d]+)S)?/
|
const timeRE = /(?<sign>-)?P(?:(?<years>[.,\d]+)Y)?(?:(?<months>[.,\d]+)M)?(?:(?<weeks>[.,\d]+)W)?(?:(?<days>[.,\d]+)D)?T(?:(?<hours>[.,\d]+)H)?(?:(?<minutes>[.,\d]+)M)?(?:(?<seconds>[.,\d]+)S)?/
|
||||||
let duration = time.match(timeRE).groups;
|
let duration = time.match(timeRE).groups;
|
||||||
console.log("match", duration)
|
console.log("match", duration)
|
||||||
if (duration) {
|
if (duration) {
|
||||||
time = [];
|
time = [];
|
||||||
|
|
||||||
|
if (duration.minutes > 60) {
|
||||||
|
duration.hours = Math.floor(duration.minutes / 60);
|
||||||
|
duration.minutes = duration.minutes % 60
|
||||||
|
}
|
||||||
|
|
||||||
if (duration.hours > 0) time.push(duration.hours + "h");
|
if (duration.hours > 0) time.push(duration.hours + "h");
|
||||||
if (duration.minutes > 0) time.push(duration.minutes + "m");
|
if (duration.minutes > 0) time.push(duration.minutes + "m");
|
||||||
time = time.join(" ");
|
time = time.join(" ");
|
||||||
|
|
@ -83,12 +106,25 @@ function highlightStep(e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ingredientMatch = /^(?:A )?([\/0-9\u00BC-\u00BE\u2153-\u215E\u2009]+) ?(.*)/
|
||||||
|
function ingredientEl(string, terms) {
|
||||||
|
//console.log("terms", terms);
|
||||||
|
|
||||||
|
let match = FRACTION_MAP.replace(clean(string)).match(ingredientMatch);
|
||||||
|
//console.log("match",match )
|
||||||
|
if (match) {
|
||||||
|
let els = match[2].split(/\s/);
|
||||||
|
els = els.map(s => terms?.has(s) ? m("i", s + " ") : s + " ")
|
||||||
|
return [m("span.quantity", match[1]), els]
|
||||||
|
}
|
||||||
|
if (string == "-") return m("hr");
|
||||||
|
return [m("span.quantity", ""), (string)];
|
||||||
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
let data = document.body.innerHTML;
|
|
||||||
document.body.innerText = "";
|
|
||||||
delete document.documentElement.style.display;
|
delete document.documentElement.style.display;
|
||||||
try {
|
try {
|
||||||
var json = JSON.parse(data);
|
var json = data; //JSON.parse(data);
|
||||||
if (json["@type"] != "Recipe") {
|
if (json["@type"] != "Recipe") {
|
||||||
json = (json["@graph"] ?? json).find((item) => item["@type"] == "Recipe")
|
json = (json["@graph"] ?? json).find((item) => item["@type"] == "Recipe")
|
||||||
}
|
}
|
||||||
|
|
@ -100,15 +136,25 @@ function render() {
|
||||||
if (!json) return;
|
if (!json) return;
|
||||||
let image = json.image;
|
let image = json.image;
|
||||||
if (Array.isArray(image)) image = image.shift();
|
if (Array.isArray(image)) image = image.shift();
|
||||||
image = image.url || image;
|
image = image?.url || image;
|
||||||
instructions = json.recipeInstructions;
|
instructions = json.recipeInstructions;
|
||||||
if (Array.isArray(instructions)) {
|
if (Array.isArray(instructions)) {
|
||||||
if (Array.isArray(instructions[0])) instructions = instructions.flat()
|
if (Array.isArray(instructions[0])) instructions = instructions.flat()
|
||||||
} else {
|
} else {
|
||||||
instructions = [{ text: instructions }];
|
instructions = [{ text: instructions }];
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.postMessage({title:json.name, favicon:"🍳"}, "*");
|
parent.postMessage({title:json.name, favicon:"🍳"}, "*");
|
||||||
|
console.log("post message")
|
||||||
|
instructions = instructions.map(i => i.text || i);
|
||||||
|
|
||||||
|
let text = instructions.join(" ");
|
||||||
|
var terms = new Set(text.split(/\s/).filter(s => s.length > 2));
|
||||||
|
instructions = instructions.map(i => m("div.step", { onclick: highlightStep }, i))
|
||||||
|
|
||||||
|
let rating = json.aggregateRating;
|
||||||
|
|
||||||
|
|
||||||
document.body.appendChild(
|
document.body.appendChild(
|
||||||
m("article.recipe", {},
|
m("article.recipe", {},
|
||||||
image ? m(".thumbnail.noprint", { style: "background-image:url(" + image + ");" }) : null,
|
image ? m(".thumbnail.noprint", { style: "background-image:url(" + image + ");" }) : null,
|
||||||
|
|
@ -118,30 +164,39 @@ function render() {
|
||||||
m("h1", json.name),
|
m("h1", json.name),
|
||||||
m(".metadata",
|
m(".metadata",
|
||||||
// json.nutrition?.calories ? m("div", (json.nutrition?.calories) + (parseFloat(json.nutrition?.calories) != NaN ? " calories" : "")) : null,
|
// json.nutrition?.calories ? m("div", (json.nutrition?.calories) + (parseFloat(json.nutrition?.calories) != NaN ? " calories" : "")) : null,
|
||||||
json.totalTime ? m("div", formatTime(json.totalTime)) : undefined,
|
|
||||||
m("div", json.recipeYield),
|
|
||||||
json.author?.name ? m(".author", (json.author?.name)) : null,
|
|
||||||
(rating = json.aggregateRating) ? [
|
|
||||||
parseFloat(rating.ratingValue).toFixed(1),
|
|
||||||
"\u2606".repeat(1),
|
|
||||||
rating.ratingCount ? " (" + rating.ratingCount + ")" : null
|
|
||||||
].join(" ") : null,
|
|
||||||
// m(".rating", (json.aggregateRating?.ratingValue), (json.aggregateRating?.ratingCount)),
|
// m(".rating", (json.aggregateRating?.ratingValue), (json.aggregateRating?.ratingCount)),
|
||||||
m("a.action.noprint", { onclick: () => window.print() }, "print"),
|
|
||||||
m("a.action.noprint", { href: json.mainEntityOfPage }, "link"),
|
|
||||||
|
|
||||||
),
|
),
|
||||||
m(".description", json.description),
|
m(".description",
|
||||||
|
clean(json.description),
|
||||||
|
json.author?.name ? m("span.author", (" —" + json.author?.name)) : null,
|
||||||
|
|
||||||
|
m("p"),
|
||||||
|
|
||||||
|
(rating && rating.ratingCount != 0) ? [
|
||||||
|
" • ",
|
||||||
|
"\u2606".repeat(rating.ratingValue), " ",
|
||||||
|
parseFloat(rating.ratingValue).toFixed(1), " ",
|
||||||
|
rating.ratingCount ? m("span.count", `(${rating.ratingCount.toString()})`) : null
|
||||||
|
] : null,
|
||||||
|
" • ", m("a.action.noprint", { onclick: () => window.print() }, "print"),
|
||||||
|
" • ", m("a.action.noprint", { href: json.mainEntityOfPage || json.url, target:"_blank"}, "link"),
|
||||||
|
|
||||||
|
),
|
||||||
|
|
||||||
),
|
),
|
||||||
m(".columns",
|
m(".columns",
|
||||||
m(".ingredients",
|
m(".ingredients",
|
||||||
json.recipeIngredient?.map(i => m("div.ingredient", { onclick: markIngredient }, FRACTION_MAP.replace(i)))
|
m("div.yield", ingredientEl(getStringProperty(json.recipeYield))),
|
||||||
|
|
||||||
|
json.recipeIngredient?.map(i => m("div.ingredient", { onclick: markIngredient }, ingredientEl(i, terms)))
|
||||||
),
|
),
|
||||||
m(".instructions",
|
m(".instructions",
|
||||||
instructions.map(i => m("div.step", { onclick: highlightStep }, i.text))
|
json.totalTime ? m("div", formatTime(json.totalTime)) : undefined,
|
||||||
|
instructions
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ function decompressDataURI(dataURI, preamble, callback) {
|
||||||
var base64Index = dataURI.indexOf(LZMA64_MARKER);
|
var base64Index = dataURI.indexOf(LZMA64_MARKER);
|
||||||
if (base64Index > 0) {
|
if (base64Index > 0) {
|
||||||
var base64 = dataURI.substring(base64Index + LZMA64_MARKER.length);
|
var base64 = dataURI.substring(base64Index + LZMA64_MARKER.length);
|
||||||
zipToString(base64, function(result) {
|
zipToString(base64, function(string) {
|
||||||
stringToData(result, function(data) {
|
stringToData(string, function(data) {
|
||||||
if (!data) return callback(undefined);
|
if (!data) return callback();
|
||||||
callback(dataURI.substring(0, base64Index) + BASE64_MARKER + (preamble || '') + data.split(',')[1])
|
callback(dataURI.substring(0, base64Index) + BASE64_MARKER + (preamble || '') + data.split(',')[1], string)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -43,6 +43,7 @@ function decompressDataURI(dataURI, preamble, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function zipToString(data, callback) {
|
function zipToString(data, callback) {
|
||||||
|
let data = data.replace("-","");
|
||||||
var array = base64ToByteArray(data);
|
var array = base64ToByteArray(data);
|
||||||
LZMA.decompress(array, function(result, error) {
|
LZMA.decompress(array, function(result, error) {
|
||||||
if (!(typeof result === 'string')) result = new Uint8Array(result)
|
if (!(typeof result === 'string')) result = new Uint8Array(result)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue