mirror of
https://github.com/arfct/itty-bitty.git
synced 2026-03-11 08:54:33 +00:00
Better recipe support and opengraph descriptions
This commit is contained in:
parent
56477e40cb
commit
c874029a45
10 changed files with 355 additions and 176 deletions
33
docs/404.html
Normal file
33
docs/404.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Page Not Found</title>
|
||||
|
||||
<style media="screen">
|
||||
body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
|
||||
#message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px 16px; border-radius: 3px; }
|
||||
#message h3 { color: #888; font-weight: normal; font-size: 16px; margin: 16px 0 12px; }
|
||||
#message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }
|
||||
#message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
|
||||
#message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }
|
||||
#message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }
|
||||
#message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
|
||||
#load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }
|
||||
@media (max-width: 600px) {
|
||||
body, #message { margin-top: 0; background: white; box-shadow: none; }
|
||||
body { border-top: 16px solid #ffa100; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="message">
|
||||
<h2>404</h2>
|
||||
<h1>Page Not Found</h1>
|
||||
<p>The specified file was not found on this website. Please check the URL for mistakes and try again.</p>
|
||||
<h3>Why am I seeing this?</h3>
|
||||
<p>This page was generated by the Firebase Command-Line Interface. To modify it, edit the <code>404.html</code> file in your project's configured <code>public</code> directory.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<!DOCTYPE html><html manifest="manifest.appcache"><meta name="viewport" content="width=device-width"><meta name="description" content="itty bitty things can be conveyed in a link.">
|
||||
<script src="lzma/lzma-d-min.js"></script>
|
||||
<script src="data.js"></script>
|
||||
|
||||
<script src="index.src/index.js"></script>
|
||||
<!DOCTYPE html><html xmanifest="manifest.appcache"><meta name="viewport" content="width=device-width, viewport-fit=cover" viewport-fit=cover"><meta name="description" content="itty bitty things can be conveyed in a link.">
|
||||
<link id="favicon" rel=icon href='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"><text y=".9em">🛰</text></svg>'>
|
||||
<script src="/lzma/lzma-d-min.js"></script>
|
||||
<script src="/data.js"></script>
|
||||
<script src="/index.src/index.js"></script>
|
||||
<style type="text/css">
|
||||
body{font-family:sans-serif}#iframe{border:0;position:absolute;top:0;left:0;width:100%;height:100%}#edit{font-family:monospace;font-weight:bold;color:rgba(0,0,0,0.54);position:absolute;z-index:100;position:absolute;top:.85em;right:1em;display:none}#edit:not(:hover){text-decoration:none}#warning{position:absolute;border-radius:4px;background-color:#feecc2;padding:1em;font-size:16px;width:20em;z-index:100;top:10vh;left:50vw;margin-left:-10em}#warning:empty{display:none}body.toasting #iframe,body.toasting #edit{opacity:.5;pointer-events:none}body.toasting #toast{box-sizing:border-box;background-color:#feecc2;border-radius:4px;font-size:13px;left:50%;top:10px;margin-left:-160px;padding:1em;position:absolute;max-width:320px;z-index:101}body:not(.toasting) #toast,body.toasting #warning{display:none}body:not(.download) #download{display:none}#download{background:#fafafa;width:100vw;height:100vh;position:absolute;top:0;left:0;display:flex;text-decoration:none;color:black;justify-content:center;align-items:center;flex-direction:column;font-size:14px}#dl-image{width:128px;height:128px;background-position:center;background-repeat:no-repeat;background-image:url("data:image/svg+xml,%0A%3Csvg width='128' height='128' viewBox='0 0 128 128' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cmask id='path-1-outside-1_116_2' maskUnits='userSpaceOnUse' x='27' y='15' width='74' height='98' fill='black'%3E%3Crect fill='white' x='27' y='15' width='74' height='98'/%3E%3Cpath d='M80 16H28V112H100V36L80 16Z'/%3E%3C/mask%3E%3Cpath d='M80 16H28V112H100V36L80 16Z' fill='white'/%3E%3Cpath d='M28 16V15H27V16H28ZM80 16L80.7071 15.2929L80.4142 15H80V16ZM28 112H27V113H28V112ZM100 112V113H101V112H100ZM100 36H101V35.5858L100.707 35.2929L100 36ZM28 17H80V15H28V17ZM29 112V16H27V112H29ZM100 111H28V113H100V111ZM99 36V112H101V36H99ZM100.707 35.2929L80.7071 15.2929L79.2929 16.7071L99.2929 36.7071L100.707 35.2929Z' fill='black' fill-opacity='0.15' mask='url(%23path-1-outside-1_116_2)'/%3E%3C/svg%3E%0A");padding:20px 32px;box-sizing:border-box;display:flex;justify-content:center;align-items:center;color:rgba(0,0,0,0.3);font-weight:bold}#dl-button{text-decoration:none;background:gray;color:white;padding:.5em 1em;border-radius:2em;display:none}#dl-button:hover{background:black}#dl-name{margin-bottom:2em}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -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,<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"><text y=".9em">'+ favicon + '</text></svg>'
|
||||
}
|
||||
|
||||
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 src="' + location.origin + '/render/recipe.js"></script>'
|
||||
script = script + " ".repeat(3 - (script.length % 3))
|
||||
console.log("script", script.length)
|
||||
preamble = btoa(script);
|
||||
} else {
|
||||
console.log("unknown type, rendering as download")
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
CACHE MANIFEST
|
||||
# v25
|
||||
# v26
|
||||
154
docs/render/recipe.css
Normal file
154
docs/render/recipe.css
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = /(?<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)
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
1
functions/.gitignore
vendored
Normal file
1
functions/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
node_modules/
|
||||
39
functions/index.js
Normal file
39
functions/index.js
Normal file
|
|
@ -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>${title}</title><meta property="og:title" content="${title}"/>`;
|
||||
if (desc) content += `<meta name="description" content="${desc}"/><meta property="og:description" content="${desc}"/>`;
|
||||
if (image) {
|
||||
if (image.startsWith("http")) {
|
||||
content += `<meta property="og:image" content="${image}"/>`;
|
||||
} else {
|
||||
image = decodeURIComponent(image)
|
||||
console.log("image", image);
|
||||
let codepoints = [];
|
||||
for (const char of image) {
|
||||
codepoints.push(char.codePointAt(0).toString(16));
|
||||
}
|
||||
content += `<link rel="apple-touch-icon" href="https://fonts.gstatic.com/s/e/notoemoji/14.0/${codepoints.join("_")}/72.png">`;
|
||||
// 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);
|
||||
}
|
||||
|
||||
});
|
||||
23
functions/package.json
Normal file
23
functions/package.json
Normal file
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in a new issue