diff --git a/docs/404.html b/docs/404.html
new file mode 100644
index 0000000..829eda8
--- /dev/null
+++ b/docs/404.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Page Not Found
+
+
+
+
+
+
404
+
Page Not Found
+
The specified file was not found on this website. Please check the URL for mistakes and try again.
+
Why am I seeing this?
+
This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.
+
+
+
diff --git a/docs/index.html b/docs/index.html
index 16f22a0..05842a0 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1,8 +1,8 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/docs/index.src/index.js b/docs/index.src/index.js
index 4d70684..47147a5 100644
--- a/docs/index.src/index.js
+++ b/docs/index.src/index.js
@@ -9,10 +9,25 @@ function dismiss() {
document.body.classList.remove("toasting")
}
-var validTypes = ["image/svg+xml", "application/ld+json"]
+function setFavicon(favicon) {
+ document.getElementById("favicon").href = 'data:image/svg+xml,'
+}
-window.onhashchange = window.onload = function() {
+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) {
location.href = "/edit";
} else {
@@ -56,7 +71,6 @@ window.onhashchange = window.onload = function() {
if (validTypes.includes(type)) {
let script = ''
script = script + " ".repeat(3 - (script.length % 3))
- console.log("script", script.length)
preamble = btoa(script);
} else {
console.log("unknown type, rendering as download")
diff --git a/docs/manifest.appcache b/docs/manifest.appcache
index 02fd817..36a229b 100644
--- a/docs/manifest.appcache
+++ b/docs/manifest.appcache
@@ -1,2 +1,2 @@
CACHE MANIFEST
-# v25
\ No newline at end of file
+# v26
\ No newline at end of file
diff --git a/docs/render/recipe.css b/docs/render/recipe.css
new file mode 100644
index 0000000..670317f
--- /dev/null
+++ b/docs/render/recipe.css
@@ -0,0 +1,154 @@
+* {
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
+}
+
+body {
+ margin: 0;
+}
+
+.thumbnail {
+ width: 100%;
+ padding-top: 56.25%;
+ background-color: gray;
+ background-size: cover;
+ background-position: center;
+}
+
+img.publisher {
+ max-width: 12em;
+ float: right;
+ margin-top: -0.5em;
+}
+
+.recipe {
+ max-width: 40em;
+ margin: auto;
+ line-height: 133%;
+}
+
+.columns {
+ display: flex;
+ gap: 2em;
+ margin-bottom: 4em;
+ align-content: center;
+ justify-content: center;
+}
+
+header {
+ gap: 2em;
+ margin-bottom: 2em;
+}
+
+h1 {
+ margin-top: 2em;
+}
+
+.metadata {
+ align: right;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0 1em;
+ margin-bottom: 1em;
+ line-height: 2em;
+}
+
+.metadata div:after {
+ content: "-";
+ margin-left: 1em;
+}
+
+.ingredients {
+ font-weight: 600;
+ font-size: 100%;
+ padding-top: 1em;
+ flex: 0 1 35%;
+}
+
+.instructions {
+ white-space: pre-wrap;
+ line-height: 125%;
+ font-size: 100%;
+ flex: 0 1 65%;
+}
+
+.ingredient {
+ padding: 0.625em 1em;
+ margin: 0 -1em;
+ line-height: 1.25em;
+ opacity: 0.75;
+ cursor: pointer;
+}
+
+.ingredient.complete,
+.step.complete {
+ text-decoration: line-through;
+ opacity: 0.33;
+}
+
+.ingredient:hover {
+ opacity: 1.0;
+ background-color: rgba(128, 128, 128, 0.1)
+}
+
+.step {
+ padding-top: 2em;
+ max-width: 40em;
+ cursor: pointer;
+}
+
+.step:before {
+ content: "";
+ width: 100px;
+ float: left;
+ margin-top: -1em;
+ height: 0.5px;
+ background-color: currentColor;
+ opacity: 0.54;
+}
+
+a.action {
+ border: none;
+ background: none;
+ text-transform: uppercase;
+ text-decoration: none;
+ color: currentColor;
+ cursor: pointer;
+ border-radius: 1em;
+ line-height: 2em;
+ padding: 0 1em;
+}
+
+a.action:hover {
+ background-color: rgba(128, 128, 128, 0.1)
+}
+
+@media screen and (max-width: 480px) {
+ /* some CSS here */
+ .columns {
+ flex-direction: column;
+ }
+ .ingredient,
+ .instructions,
+ header {
+ padding: 0 1em;
+ }
+ .ingredient {
+ margin: 0;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: #222;
+ color: white;
+ }
+}
+
+@media print {
+ body {
+ font-size: 14px;
+ }
+ .noprint {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/docs/render/recipe.js b/docs/render/recipe.js
index c76dabf..3c41ae4 100644
--- a/docs/render/recipe.js
+++ b/docs/render/recipe.js
@@ -1,14 +1,31 @@
+let script = document.currentScript;
+
+
FRACTION_MAP = {
- '1/2': '\u00BD', '1/4': '\u00BC', '3/4': '\u00BE', '1/3': '\u2153', '2/3': '\u2154', '1/5': '\u2155', '2/5': '\u2156', '3/5': '\u2157', '4/5': '\u2158', '1/6': '\u2159', '5/6': '\u215A', '1/8': '\u215B', '3/8': '\u215C', '5/8': '\u215D', '7/8': '\u215E',
+ '1/2': '\u00BD',
+ '1/4': '\u00BC',
+ '3/4': '\u00BE',
+ '1/3': '\u2153',
+ '2/3': '\u2154',
+ '1/5': '\u2155',
+ '2/5': '\u2156',
+ '3/5': '\u2157',
+ '4/5': '\u2158',
+ '1/6': '\u2159',
+ '5/6': '\u215A',
+ '1/8': '\u215B',
+ '3/8': '\u215C',
+ '5/8': '\u215D',
+ '7/8': '\u215E',
replace: function(string) {
- return string.replace(/\d\/\d/g, function(a,b,c) {
+ return string.replace(/\d\/\d/g, function(a, b, c) {
return FRACTION_MAP[a];
})
}
}
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() : {};
let classes = selector.split(".");
if (classes.length > 0) selector = classes.shift();
@@ -22,12 +39,12 @@ const m = (selector, ...args) => {
for (let prop in attrs) {
if (attrs.hasOwnProperty(prop) && attrs[prop] != undefined) {
if (prop.indexOf("data-") == 0) {
- let dataProp = prop.substring(5).replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
+ let dataProp = prop.substring(5).replace(/-([a-z])/g, function(g) { return g[1].toUpperCase(); });
node.dataset[dataProp] = attrs[prop];
} else {
node[prop] = attrs[prop];
}
- }
+ }
}
const append = (child) => {
@@ -42,11 +59,11 @@ const m = (selector, ...args) => {
function formatTime(time) {
const timeRE = /(?-)?P(?:(?[.,\d]+)Y)?(?:(?[.,\d]+)M)?(?:(?[.,\d]+)W)?(?:(?[.,\d]+)D)?T(?:(?[.,\d]+)H)?(?:(?[.,\d]+)M)?(?:(?[.,\d]+)S)?/
- let duration = time.match(timeRE)?.groups;
+ let duration = time.match(timeRE).groups;
console.log("match", duration)
if (duration) {
time = [];
- 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");
time = time.join(" ");
}
@@ -56,26 +73,31 @@ function formatTime(time) {
function markIngredient(e) {
e.target.classList.toggle("complete")
}
+
function highlightStep(e) {
-// console.log("e", e.target)
-// e.target.parent.children.forEach((i,el) => {
-// el.classList.toggle("complete", )
-// }
+ // console.log("e", e.target)
+ // e.target.parent.children.forEach((i,el) => {
+ // el.classList.toggle("complete", )
+ // }
e.target.classList.toggle("complete")
}
function render() {
- let data = document.body.innerText;
+ let data = document.body.innerHTML;
document.body.innerText = "";
delete document.documentElement.style.display;
- let json = JSON.parse(data);
-
- if (json["@type"] != "Recipe") {
- json = (json["@graph"] ?? json).find((item) => item["@type"] == "Recipe")
+ try {
+ var json = JSON.parse(data);
+ if (json["@type"] != "Recipe") {
+ json = (json["@graph"] ?? json).find((item) => item["@type"] == "Recipe")
+ }
+ console.log("Recipe", json);
+ } catch (e) {
+ console.log("Data", e, {data});
}
- console.log("recipe", json)
+ if (!json) return;
let image = json.image;
if (Array.isArray(image)) image = image.shift();
image = image.url || image;
@@ -83,161 +105,55 @@ function render() {
if (Array.isArray(instructions)) {
if (Array.isArray(instructions[0])) instructions = instructions.flat()
} else {
- instructions = [{text:instructions}];
+ instructions = [{ text: instructions }];
}
- console.log(instructions);
+ parent.postMessage({title:json.name, favicon:"🍳"}, "*");
+
document.body.appendChild(
- m("article.recipe",{},
- m("img.publisher", {src:json.publisher?.image[0]?.url}),
- m("img.thumbnail.noprint", {src:image}),
- m("header",
+ m("article.recipe", {},
+ image ? m(".thumbnail.noprint", { style: "background-image:url(" + image + ");" }) : null,
+
+ m("header",
+ m("img.publisher", { src: json.publisher?.image ?.[0]?.url ?? json.publisher ?.logo ?.url }),
m("h1", json.name),
m(".metadata",
- json.nutrition?.calories ? m("div", (json.nutrition?.calories) + " calories") : null,
- m("div", json.recipeYield),
- json.totalTime ? m("div", formatTime(json.totalTime)) : undefined,
- json.author?.name ? m(".author", (json.author?.name)) : null,
- "\u2605".repeat(json.aggregateRating?.ratingValue) + " " + parseFloat(json.aggregateRating?.ratingValue).toFixed(1) + " (" + json.aggregateRating?.ratingCount + ")",
- // m(".rating", (json.aggregateRating?.ratingValue), (json.aggregateRating?.ratingCount)),
- m("button.noprint", {onclick:() => window.print()},"print"),
- m("a", {href:json.mainEntityOfPage}, "LINK"),
+ // 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("a.action.noprint", { onclick: () => window.print() }, "print"),
+ m("a.action.noprint", { href: json.mainEntityOfPage }, "link"),
- ),
- m(".description", json.description),
+ ),
+ m(".description", json.description),
),
m(".columns",
m(".ingredients",
- json.recipeIngredient?.map(i => m("div.ingredient", {onclick:markIngredient}, FRACTION_MAP.replace(i)))
+ json.recipeIngredient?.map(i => m("div.ingredient", { onclick: markIngredient }, FRACTION_MAP.replace(i)))
),
m(".instructions",
- instructions.map(i => m("div.step", {onclick:highlightStep}, i.text))
+ instructions.map(i => m("div.step", { onclick: highlightStep }, i.text))
)
)
)
)
- document.head.appendChild(m("style", {}, `
- * {
- font-family:-apple-system, BlinkMacSystemFont, sans-serif;
- }
+ var path = script.src.substring(0, script.src.lastIndexOf("."));
+ var cssURL = path + ".css";
- img.thumbnail {
- width:100%;
- }
-
- img.publisher {
- max-width: 12em;
- margin-bottom:1em;
- }
- .recipe {
- max-width:40em;
- margin:auto;
- line-height:133%;
- }
-
- .columns {
- display:flex;
- gap:2em;
- margin-bottom:4em;
- align-content:center;
- justify-content:center;
- }
- header {
- gap:2em;
- margin-bottom:2em;
- }
- h1 {
- margin-top:2em;
-
- }
-
- .metadata {
- align:right;
- display:flex;
- gap:1em;
- margin-bottom:1em;
- }
- .metadata div:after {
- content:"-";
- margin-left:1em;
- }
- .ingredients {
- font-weight:600;
- font-size:100%;
- padding-top:1em;
- flex:0 1 35%;
- }
-
- .instructions {
- white-space: pre-wrap;
- line-height:125%;
- font-size:100%;
- flex: 0 1 65%;
- }
-
- .ingredient {
- padding: 0.625em 1em;
- margin:0 -1em;
- line-height:1.25em;
- opacity:0.75;
- cursor:pointer;
- }
- .ingredient.complete,
- .step.complete {
- text-decoration:line-through;
- opacity:0.33;
- }
- .ingredient:hover {
- opacity:1.0;
- background-color:rgba(128,128,128,0.1)
- }
-
- .step {
- padding-top:2em;
- max-width:40em;
-
- cursor:pointer;
- }
- .step:before {
- content:"";
- width:100px;
- float:left;
- margin-top:-1em;
- height:0.5px;
- background-color:currentColor;
- opacity:0.54;
- }
-
- button {
- border:none;
- background:none;
- text-transform:uppercase;
- }
-
- @media screen and (max-width: 480px) {
- /* some CSS here */
- .columns {
- flex-direction:column;
- }
- }
-
- @media (prefers-color-scheme: dark) {
- body {
- background-color:#222;
- color:white;
- }
- }
- @media print {
- body { font-size:14px; }
- .noprint { display:none; }
- }
-
-
- `));
+ let style = m("link", { rel: "stylesheet", type: "text/css", href: cssURL })
+ document.head.appendChild(style);
}
+
window.addEventListener('load', (event) => {
render();
-});
+});
\ No newline at end of file
diff --git a/firebase.json b/firebase.json
index b4e14b4..f6b60a7 100644
--- a/firebase.json
+++ b/firebase.json
@@ -2,24 +2,23 @@
"hosting": {
"public": "docs",
"ignore": [
- "firebase.json",
- "**/.*",
- "**/node_modules/**",
- "samples/**",
- "src/**",
- "LICENSE",
"**.md",
- "**/.git/**",
"**.sh",
- "index.src/**"
+ "**/.*",
+ "**/.git/**",
+ "**/node_modules/**",
+ "firebase.json",
+ "index.src/**",
+ "LICENSE",
+ "samples/**",
+ "src/**"
],
"rewrites": [
{
- "source": "**",
- "destination": "/index.html"
+ "source": "/**/",
+ "function": "index"
}
- ],
- "cleanUrls": true,
- "trailingSlash": false
+ ]
}
-}
\ No newline at end of file
+}
+
diff --git a/functions/.gitignore b/functions/.gitignore
new file mode 100644
index 0000000..40b878d
--- /dev/null
+++ b/functions/.gitignore
@@ -0,0 +1 @@
+node_modules/
\ No newline at end of file
diff --git a/functions/index.js b/functions/index.js
new file mode 100644
index 0000000..05e9050
--- /dev/null
+++ b/functions/index.js
@@ -0,0 +1,39 @@
+const functions = require("firebase-functions");
+
+exports.index = functions.https.onRequest((request, response) => {
+ functions.logger.info("Hello logs!", );
+ const agent = request.headers["user-agent"];
+ let path = request.path;
+ if (agent.indexOf("Twitterbot") != -1 || agent.indexOf("facebookexternalhit") != -1) {
+ let components = path.split("/");
+ components.shift();
+ components.pop();
+ let title = components.shift().replace("_", " ");
+ let desc = components.shift().replace("_", " ");
+ let image = components.join("/");
+ console.log("components", title, desc, image)
+
+ let content = "";
+ if (title) content += `${title}`;
+ if (desc) content += ``;
+ if (image) {
+ if (image.startsWith("http")) {
+ content += ``;
+ } else {
+ image = decodeURIComponent(image)
+ console.log("image", image);
+ let codepoints = [];
+ for (const char of image) {
+ codepoints.push(char.codePointAt(0).toString(16));
+ }
+ content += ``;
+ // https://fonts.gstatic.com/s/e/notoemoji/14.0/1f468_1f3fd_200d_1f91d_200d_1f468_1f3fc/72.png
+ // https://fonts.gstatic.com/s/e/notoemoji/14.0/1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc/72.png
+ }
+ }
+ response.send(content);
+ } else {
+ response.redirect("/?" + path);
+ }
+
+});
\ No newline at end of file
diff --git a/functions/package.json b/functions/package.json
new file mode 100644
index 0000000..1dae836
--- /dev/null
+++ b/functions/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "functions",
+ "description": "Cloud Functions for Firebase",
+ "scripts": {
+ "serve": "firebase emulators:start --only functions",
+ "shell": "firebase functions:shell",
+ "start": "npm run shell",
+ "deploy": "firebase deploy --only functions",
+ "logs": "firebase functions:log"
+ },
+ "engines": {
+ "node": "16"
+ },
+ "main": "index.js",
+ "dependencies": {
+ "firebase-admin": "^9.8.0",
+ "firebase-functions": "^3.14.1"
+ },
+ "devDependencies": {
+ "firebase-functions-test": "^0.2.0"
+ },
+ "private": true
+}