Recipes
Dinner
// === π½οΈ Flexible Recipe Browser ===
// CONFIG: Change these variables per block
const courseFilter = "dinner"; // <== Set the course you want to show here
const recipesPerPage = 4;
// === Fetch, Filter + Sort Pages ===
const allPages = dv.pages('"content"')
.where(p => p["total-time"] > 0 && p.course && p.course.toLowerCase() === courseFilter.toLowerCase())
.sort(p => p.rating ?? 0, 'desc');
let currentPage = 1;
// === Create grid container ===
const container = document.createElement("div");
dv.container.appendChild(container);
const grid = document.createElement("div");
Object.assign(grid.style, {
display: "flex",
flexWrap: "wrap",
gap: "20px",
justifyContent: "flex-start",
marginBottom: "20px",
});
container.appendChild(grid);
// === Pagination controls ===
const pagination = document.createElement("div");
Object.assign(pagination.style, {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginTop: "10px"
});
container.appendChild(pagination);
// === Render function ===
async function renderPage() {
grid.innerHTML = "";
pagination.innerHTML = "";
const start = (currentPage - 1) * recipesPerPage;
const end = start + recipesPerPage;
const currentRecipes = allPages.slice(start, end);
for (const page of currentRecipes) {
const card = await createRecipeCard(page);
grid.appendChild(card);
}
// Pagination Buttons
const totalPages = Math.ceil(allPages.length / recipesPerPage);
const prevBtn = document.createElement("button");
prevBtn.textContent = "β¬
οΈ Prev";
prevBtn.disabled = currentPage === 1;
styleButton(prevBtn);
prevBtn.onclick = () => {
if (currentPage > 1) {
currentPage--;
renderPage();
}
};
pagination.appendChild(prevBtn);
const pageInfo = document.createElement("span");
pageInfo.textContent = `Page ${currentPage} of ${totalPages}`;
Object.assign(pageInfo.style, {
fontSize: "0.9em",
color: "var(--text-muted)",
});
pagination.appendChild(pageInfo);
const nextBtn = document.createElement("button");
nextBtn.textContent = "Next β‘οΈ";
nextBtn.disabled = currentPage >= totalPages;
styleButton(nextBtn);
nextBtn.onclick = () => {
if (currentPage < totalPages) {
currentPage++;
renderPage();
}
};
pagination.appendChild(nextBtn);
}
// === Helper to style buttons ===
function styleButton(btn) {
Object.assign(btn.style, {
padding: "6px 12px",
border: "1px solid var(--background-modifier-border)",
borderRadius: "6px",
background: "transparent",
color: "var(--text-normal)",
cursor: "pointer",
backgroundColor: "var(--background-secondary-alt)",
});
btn.onmouseover = () => (btn.style.backgroundColor = "var(--background-modifier-hover)");
btn.onmouseout = () => (btn.style.backgroundColor = "var(--background-secondary-alt)");
}
// === Async card creator ===
async function createRecipeCard(page) {
const card = document.createElement("div");
Object.assign(card.style, {
border: "1px solid var(--background-modifier-border)",
borderRadius: "12px",
padding: "16px",
width: "240px",
boxSizing: "border-box",
backgroundColor: "var(--background-primary)",
boxShadow: "0 2px 6px rgba(0,0,0,0.1)",
display: "flex",
flexDirection: "column",
cursor: "pointer",
transition: "transform 0.2s ease",
});
card.onmouseover = () => { card.style.transform = "scale(1.03)" };
card.onmouseout = () => { card.style.transform = "scale(1)" };
// === Load image ===
let imgSrc = null;
try {
const content = await dv.io.load(page.file.path);
const match = content.match(new RegExp("!\\[\\[(.*?)\\]\\]"));
imgSrc = match ? match[1] : null;
} catch (err) {
console.error(`Could not load ${page.file.path}`, err);
}
if (imgSrc) {
const file = app.metadataCache.getFirstLinkpathDest(imgSrc, page.file.path);
if (file) {
const imgDiv = document.createElement("div");
Object.assign(imgDiv.style, {
width: "100%",
height: "150px",
backgroundImage: `url(${app.vault.getResourcePath(file)})`,
backgroundSize: "cover",
backgroundPosition: "center",
borderRadius: "8px",
marginBottom: "12px",
});
card.appendChild(imgDiv);
}
}
// === Title ===
const title = document.createElement("div");
title.textContent = page.title ?? page.file.name;
Object.assign(title.style, {
fontWeight: "600",
fontSize: "1.1em",
color: "var(--text-accent)",
marginBottom: "8px",
textAlign: "center",
});
title.onclick = () => app.workspace.openLinkText(page.file.name, page.file.path);
card.appendChild(title);
// === Metadata ===
const fields = [
{ label: "β±οΈ Prep", value: page["prep-time"] ? `${page["prep-time"]} min` : "β" },
{ label: "π³ Cook", value: page["cook-time"] ? `${page["cook-time"]} min` : "β" },
{ label: "π Total", value: page["total-time"] ? `${page["total-time"]} min` : "β" },
{ label: "π½οΈ Servings", value: page.servings ?? "β" },
{ label: "β Rating", value: page.rating ?? "β" },
];
fields.forEach(f => {
const line = document.createElement("div");
line.textContent = `${f.label}: ${f.value}`;
Object.assign(line.style, {
fontSize: "0.9em",
marginTop: "4px",
color: "var(--text-muted)",
});
card.appendChild(line);
});
return card;
}
// === Initial render ===
renderPage();
Dessert
// === π½οΈ Flexible Recipe Browser ===
// CONFIG: Change these variables per block
const courseFilter = "dessert"; // <== Set the course you want to show here
const recipesPerPage = 4;
// === Fetch, Filter + Sort Pages ===
const allPages = dv.pages('"content"')
.where(p => p["total-time"] > 0 && p.course && p.course.toLowerCase() === courseFilter.toLowerCase())
.sort(p => p.rating ?? 0, 'desc');
let currentPage = 1;
// === Create grid container ===
const container = document.createElement("div");
dv.container.appendChild(container);
const grid = document.createElement("div");
Object.assign(grid.style, {
display: "flex",
flexWrap: "wrap",
gap: "20px",
justifyContent: "flex-start",
marginBottom: "20px",
});
container.appendChild(grid);
// === Pagination controls ===
const pagination = document.createElement("div");
Object.assign(pagination.style, {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginTop: "10px"
});
container.appendChild(pagination);
// === Render function ===
async function renderPage() {
grid.innerHTML = "";
pagination.innerHTML = "";
const start = (currentPage - 1) * recipesPerPage;
const end = start + recipesPerPage;
const currentRecipes = allPages.slice(start, end);
for (const page of currentRecipes) {
const card = await createRecipeCard(page);
grid.appendChild(card);
}
// Pagination Buttons
const totalPages = Math.ceil(allPages.length / recipesPerPage);
const prevBtn = document.createElement("button");
prevBtn.textContent = "β¬
οΈ Prev";
prevBtn.disabled = currentPage === 1;
styleButton(prevBtn);
prevBtn.onclick = () => {
if (currentPage > 1) {
currentPage--;
renderPage();
}
};
pagination.appendChild(prevBtn);
const pageInfo = document.createElement("span");
pageInfo.textContent = `Page ${currentPage} of ${totalPages}`;
Object.assign(pageInfo.style, {
fontSize: "0.9em",
color: "var(--text-muted)",
});
pagination.appendChild(pageInfo);
const nextBtn = document.createElement("button");
nextBtn.textContent = "Next β‘οΈ";
nextBtn.disabled = currentPage >= totalPages;
styleButton(nextBtn);
nextBtn.onclick = () => {
if (currentPage < totalPages) {
currentPage++;
renderPage();
}
};
pagination.appendChild(nextBtn);
}
// === Helper to style buttons ===
function styleButton(btn) {
Object.assign(btn.style, {
padding: "6px 12px",
border: "1px solid var(--background-modifier-border)",
borderRadius: "6px",
background: "transparent",
color: "var(--text-normal)",
cursor: "pointer",
backgroundColor: "var(--background-secondary-alt)",
});
btn.onmouseover = () => (btn.style.backgroundColor = "var(--background-modifier-hover)");
btn.onmouseout = () => (btn.style.backgroundColor = "var(--background-secondary-alt)");
}
// === Async card creator ===
async function createRecipeCard(page) {
const card = document.createElement("div");
Object.assign(card.style, {
border: "1px solid var(--background-modifier-border)",
borderRadius: "12px",
padding: "16px",
width: "240px",
boxSizing: "border-box",
backgroundColor: "var(--background-primary)",
boxShadow: "0 2px 6px rgba(0,0,0,0.1)",
display: "flex",
flexDirection: "column",
cursor: "pointer",
transition: "transform 0.2s ease",
});
card.onmouseover = () => { card.style.transform = "scale(1.03)" };
card.onmouseout = () => { card.style.transform = "scale(1)" };
// === Load image ===
let imgSrc = null;
try {
const content = await dv.io.load(page.file.path);
const match = content.match(new RegExp("!\\[\\[(.*?)\\]\\]"));
imgSrc = match ? match[1] : null;
} catch (err) {
console.error(`Could not load ${page.file.path}`, err);
}
if (imgSrc) {
const file = app.metadataCache.getFirstLinkpathDest(imgSrc, page.file.path);
if (file) {
const imgDiv = document.createElement("div");
Object.assign(imgDiv.style, {
width: "100%",
height: "150px",
backgroundImage: `url(${app.vault.getResourcePath(file)})`,
backgroundSize: "cover",
backgroundPosition: "center",
borderRadius: "8px",
marginBottom: "12px",
});
card.appendChild(imgDiv);
}
}
// === Title ===
const title = document.createElement("div");
title.textContent = page.title ?? page.file.name;
Object.assign(title.style, {
fontWeight: "600",
fontSize: "1.1em",
color: "var(--text-accent)",
marginBottom: "8px",
textAlign: "center",
});
title.onclick = () => app.workspace.openLinkText(page.file.name, page.file.path);
card.appendChild(title);
// === Metadata ===
const fields = [
{ label: "β±οΈ Prep", value: page["prep-time"] ? `${page["prep-time"]} min` : "β" },
{ label: "π³ Cook", value: page["cook-time"] ? `${page["cook-time"]} min` : "β" },
{ label: "π Total", value: page["total-time"] ? `${page["total-time"]} min` : "β" },
{ label: "π½οΈ Servings", value: page.servings ?? "β" },
{ label: "β Rating", value: page.rating ?? "β" },
];
fields.forEach(f => {
const line = document.createElement("div");
line.textContent = `${f.label}: ${f.value}`;
Object.assign(line.style, {
fontSize: "0.9em",
marginTop: "4px",
color: "var(--text-muted)",
});
card.appendChild(line);
});
return card;
}
// === Initial render ===
renderPage();
Sauce
// === π½οΈ Flexible Recipe Browser ===
// CONFIG: Change these variables per block
const courseFilter = "sauce"; // <== Set the course you want to show here
const recipesPerPage = 4;
// === Fetch, Filter + Sort Pages ===
const allPages = dv.pages('"content"')
.where(p => p["total-time"] > 0 && p.course && p.course.toLowerCase() === courseFilter.toLowerCase())
.sort(p => p.rating ?? 0, 'desc');
let currentPage = 1;
// === Create grid container ===
const container = document.createElement("div");
dv.container.appendChild(container);
const grid = document.createElement("div");
Object.assign(grid.style, {
display: "flex",
flexWrap: "wrap",
gap: "20px",
justifyContent: "flex-start",
marginBottom: "20px",
});
container.appendChild(grid);
// === Pagination controls ===
const pagination = document.createElement("div");
Object.assign(pagination.style, {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginTop: "10px"
});
container.appendChild(pagination);
// === Render function ===
async function renderPage() {
grid.innerHTML = "";
pagination.innerHTML = "";
const start = (currentPage - 1) * recipesPerPage;
const end = start + recipesPerPage;
const currentRecipes = allPages.slice(start, end);
for (const page of currentRecipes) {
const card = await createRecipeCard(page);
grid.appendChild(card);
}
// Pagination Buttons
const totalPages = Math.ceil(allPages.length / recipesPerPage);
const prevBtn = document.createElement("button");
prevBtn.textContent = "β¬
οΈ Prev";
prevBtn.disabled = currentPage === 1;
styleButton(prevBtn);
prevBtn.onclick = () => {
if (currentPage > 1) {
currentPage--;
renderPage();
}
};
pagination.appendChild(prevBtn);
const pageInfo = document.createElement("span");
pageInfo.textContent = `Page ${currentPage} of ${totalPages}`;
Object.assign(pageInfo.style, {
fontSize: "0.9em",
color: "var(--text-muted)",
});
pagination.appendChild(pageInfo);
const nextBtn = document.createElement("button");
nextBtn.textContent = "Next β‘οΈ";
nextBtn.disabled = currentPage >= totalPages;
styleButton(nextBtn);
nextBtn.onclick = () => {
if (currentPage < totalPages) {
currentPage++;
renderPage();
}
};
pagination.appendChild(nextBtn);
}
// === Helper to style buttons ===
function styleButton(btn) {
Object.assign(btn.style, {
padding: "6px 12px",
border: "1px solid var(--background-modifier-border)",
borderRadius: "6px",
background: "transparent",
color: "var(--text-normal)",
cursor: "pointer",
backgroundColor: "var(--background-secondary-alt)",
});
btn.onmouseover = () => (btn.style.backgroundColor = "var(--background-modifier-hover)");
btn.onmouseout = () => (btn.style.backgroundColor = "var(--background-secondary-alt)");
}
// === Async card creator ===
async function createRecipeCard(page) {
const card = document.createElement("div");
Object.assign(card.style, {
border: "1px solid var(--background-modifier-border)",
borderRadius: "12px",
padding: "16px",
width: "240px",
boxSizing: "border-box",
backgroundColor: "var(--background-primary)",
boxShadow: "0 2px 6px rgba(0,0,0,0.1)",
display: "flex",
flexDirection: "column",
cursor: "pointer",
transition: "transform 0.2s ease",
});
card.onmouseover = () => { card.style.transform = "scale(1.03)" };
card.onmouseout = () => { card.style.transform = "scale(1)" };
// === Load image ===
let imgSrc = null;
try {
const content = await dv.io.load(page.file.path);
const match = content.match(new RegExp("!\\[\\[(.*?)\\]\\]"));
imgSrc = match ? match[1] : null;
} catch (err) {
console.error(`Could not load ${page.file.path}`, err);
}
if (imgSrc) {
const file = app.metadataCache.getFirstLinkpathDest(imgSrc, page.file.path);
if (file) {
const imgDiv = document.createElement("div");
Object.assign(imgDiv.style, {
width: "100%",
height: "150px",
backgroundImage: `url(${app.vault.getResourcePath(file)})`,
backgroundSize: "cover",
backgroundPosition: "center",
borderRadius: "8px",
marginBottom: "12px",
});
card.appendChild(imgDiv);
}
}
// === Title ===
const title = document.createElement("div");
title.textContent = page.title ?? page.file.name;
Object.assign(title.style, {
fontWeight: "600",
fontSize: "1.1em",
color: "var(--text-accent)",
marginBottom: "8px",
textAlign: "center",
});
title.onclick = () => app.workspace.openLinkText(page.file.name, page.file.path);
card.appendChild(title);
// === Metadata ===
const fields = [
{ label: "β±οΈ Prep", value: page["prep-time"] ? `${page["prep-time"]} min` : "β" },
{ label: "π³ Cook", value: page["cook-time"] ? `${page["cook-time"]} min` : "β" },
{ label: "π Total", value: page["total-time"] ? `${page["total-time"]} min` : "β" },
{ label: "π½οΈ Servings", value: page.servings ?? "β" },
{ label: "β Rating", value: page.rating ?? "β" },
];
fields.forEach(f => {
const line = document.createElement("div");
line.textContent = `${f.label}: ${f.value}`;
Object.assign(line.style, {
fontSize: "0.9em",
marginTop: "4px",
color: "var(--text-muted)",
});
card.appendChild(line);
});
return card;
}
// === Initial render ===
renderPage();
Baking
// === π½οΈ Flexible Recipe Browser ===
// CONFIG: Change these variables per block
const courseFilter = "baking"; // <== Set the course you want to show here
const recipesPerPage = 4;
// === Fetch, Filter + Sort Pages ===
const allPages = dv.pages('"content"')
.where(p => p["total-time"] > 0 && p.course && p.course.toLowerCase() === courseFilter.toLowerCase())
.sort(p => p.rating ?? 0, 'desc');
let currentPage = 1;
// === Create grid container ===
const container = document.createElement("div");
dv.container.appendChild(container);
const grid = document.createElement("div");
Object.assign(grid.style, {
display: "flex",
flexWrap: "wrap",
gap: "20px",
justifyContent: "flex-start",
marginBottom: "20px",
});
container.appendChild(grid);
// === Pagination controls ===
const pagination = document.createElement("div");
Object.assign(pagination.style, {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginTop: "10px"
});
container.appendChild(pagination);
// === Render function ===
async function renderPage() {
grid.innerHTML = "";
pagination.innerHTML = "";
const start = (currentPage - 1) * recipesPerPage;
const end = start + recipesPerPage;
const currentRecipes = allPages.slice(start, end);
for (const page of currentRecipes) {
const card = await createRecipeCard(page);
grid.appendChild(card);
}
// Pagination Buttons
const totalPages = Math.ceil(allPages.length / recipesPerPage);
const prevBtn = document.createElement("button");
prevBtn.textContent = "β¬
οΈ Prev";
prevBtn.disabled = currentPage === 1;
styleButton(prevBtn);
prevBtn.onclick = () => {
if (currentPage > 1) {
currentPage--;
renderPage();
}
};
pagination.appendChild(prevBtn);
const pageInfo = document.createElement("span");
pageInfo.textContent = `Page ${currentPage} of ${totalPages}`;
Object.assign(pageInfo.style, {
fontSize: "0.9em",
color: "var(--text-muted)",
});
pagination.appendChild(pageInfo);
const nextBtn = document.createElement("button");
nextBtn.textContent = "Next β‘οΈ";
nextBtn.disabled = currentPage >= totalPages;
styleButton(nextBtn);
nextBtn.onclick = () => {
if (currentPage < totalPages) {
currentPage++;
renderPage();
}
};
pagination.appendChild(nextBtn);
}
// === Helper to style buttons ===
function styleButton(btn) {
Object.assign(btn.style, {
padding: "6px 12px",
border: "1px solid var(--background-modifier-border)",
borderRadius: "6px",
background: "transparent",
color: "var(--text-normal)",
cursor: "pointer",
backgroundColor: "var(--background-secondary-alt)",
});
btn.onmouseover = () => (btn.style.backgroundColor = "var(--background-modifier-hover)");
btn.onmouseout = () => (btn.style.backgroundColor = "var(--background-secondary-alt)");
}
// === Async card creator ===
async function createRecipeCard(page) {
const card = document.createElement("div");
Object.assign(card.style, {
border: "1px solid var(--background-modifier-border)",
borderRadius: "12px",
padding: "16px",
width: "240px",
boxSizing: "border-box",
backgroundColor: "var(--background-primary)",
boxShadow: "0 2px 6px rgba(0,0,0,0.1)",
display: "flex",
flexDirection: "column",
cursor: "pointer",
transition: "transform 0.2s ease",
});
card.onmouseover = () => { card.style.transform = "scale(1.03)" };
card.onmouseout = () => { card.style.transform = "scale(1)" };
// === Load image ===
let imgSrc = null;
try {
const content = await dv.io.load(page.file.path);
const match = content.match(new RegExp("!\\[\\[(.*?)\\]\\]"));
imgSrc = match ? match[1] : null;
} catch (err) {
console.error(`Could not load ${page.file.path}`, err);
}
if (imgSrc) {
const file = app.metadataCache.getFirstLinkpathDest(imgSrc, page.file.path);
if (file) {
const imgDiv = document.createElement("div");
Object.assign(imgDiv.style, {
width: "100%",
height: "150px",
backgroundImage: `url(${app.vault.getResourcePath(file)})`,
backgroundSize: "cover",
backgroundPosition: "center",
borderRadius: "8px",
marginBottom: "12px",
});
card.appendChild(imgDiv);
}
}
// === Title ===
const title = document.createElement("div");
title.textContent = page.title ?? page.file.name;
Object.assign(title.style, {
fontWeight: "600",
fontSize: "1.1em",
color: "var(--text-accent)",
marginBottom: "8px",
textAlign: "center",
});
title.onclick = () => app.workspace.openLinkText(page.file.name, page.file.path);
card.appendChild(title);
// === Metadata ===
const fields = [
{ label: "β±οΈ Prep", value: page["prep-time"] ? `${page["prep-time"]} min` : "β" },
{ label: "π³ Cook", value: page["cook-time"] ? `${page["cook-time"]} min` : "β" },
{ label: "π Total", value: page["total-time"] ? `${page["total-time"]} min` : "β" },
{ label: "π½οΈ Servings", value: page.servings ?? "β" },
{ label: "β Rating", value: page.rating ?? "β" },
];
fields.forEach(f => {
const line = document.createElement("div");
line.textContent = `${f.label}: ${f.value}`;
Object.assign(line.style, {
fontSize: "0.9em",
marginTop: "4px",
color: "var(--text-muted)",
});
card.appendChild(line);
});
return card;
}
// === Initial render ===
renderPage();