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();