diff --git a/20260307233451-recipes_cookbook.org b/20260307233451-recipes_cookbook.org index 3d20203..78d903a 100644 --- a/20260307233451-recipes_cookbook.org +++ b/20260307233451-recipes_cookbook.org @@ -1,5 +1,112 @@ :PROPERTIES: :ID: b31d6c3f-9bc3-4006-aaaf-d56168249e3a :END: -#+title: Recipes Cookbook +#+title: Recipes Cookbook +#+AUTHOR: Auto-generated +#+filetags: :recipes:cooking:org:index: +#+STARTUP: content +#+PROPERTY: GENERATED_AT 2026-03-08T14:43:28.313070 + +# How to add notes: under any recipe heading, add :COOKED: to record when you made it, and :TIPS: your tip text to add notes. +# Multiple :COOKED: lines are supported. These are preserved across regenerations. + +* Lunch + +** Chicken Rice Meal Prep :Chicken:Chicken_and_Rice:Lunch:Rice: +:PROPERTIES: +:RECIPE_ID: 23153 +:CATEGORY: Lunch +:KEYWORDS: Chicken,Chicken and Rice,Lunch,Rice +:CREATED: 2026-03-08T00:03:56+0000 +:MODIFIED: 2026-03-08T00:12:24+0000 +:URL: https://nextcloud.zainezq.com/apps/cookbook/webapp/recipes/23153 +:COOKED: <2026-03-08 Sun> +:END: +#+ATTR_HTML: :class recipe-meta +| | | +|---|---| +| *Category* | Lunch | +| *Keywords* | Chicken,Chicken and Rice,Lunch,Rice | +| *Created* | 2026-03-08 | +| *Modified* | 2026-03-08 | +| *Cooked* | <2026-03-08 Sun> | +| *Link* | [[https://nextcloud.zainezq.com/apps/cookbook/webapp/recipes/23153][Open in Nextcloud]] | + +** Healthy CREAMY Tuna Wraps :Lunch:Tuna: +:PROPERTIES: +:RECIPE_ID: 23133 +:CATEGORY: Lunch +:KEYWORDS: Lunch,Tuna +:CREATED: 2026-03-07T23:02:59+0000 +:MODIFIED: 2026-03-08T00:12:38+0000 +:URL: https://nextcloud.zainezq.com/apps/cookbook/webapp/recipes/23133 +:COOKED: <2025-07-17 Thu> +:END: +#+ATTR_HTML: :class recipe-meta +| | | +|---|---| +| *Category* | Lunch | +| *Keywords* | Lunch,Tuna | +| *Created* | 2026-03-07 | +| *Modified* | 2026-03-08 | +| *Cooked* | <2025-07-17 Thu> | +| *Link* | [[https://nextcloud.zainezq.com/apps/cookbook/webapp/recipes/23133][Open in Nextcloud]] | + +** High Protein Tuna Salad :Lunch:Tuna: +:PROPERTIES: +:RECIPE_ID: 21929 +:CATEGORY: Lunch +:KEYWORDS: Lunch,Tuna +:CREATED: 2026-02-23T16:49:56+0000 +:MODIFIED: 2026-03-08T00:12:47+0000 +:URL: https://nextcloud.zainezq.com/apps/cookbook/webapp/recipes/21929 +:END: +#+ATTR_HTML: :class recipe-meta +| | | +|---|---| +| *Category* | Lunch | +| *Keywords* | Lunch,Tuna | +| *Created* | 2026-02-23 | +| *Modified* | 2026-03-08 | +| *Cooked* | /not yet made/ | +| *Link* | [[https://nextcloud.zainezq.com/apps/cookbook/webapp/recipes/21929][Open in Nextcloud]] | + +** One Pot Chicken and Rice Recipe :Chicken:Chicken_and_Rice:Lunch:One_Pot:Rice: +:PROPERTIES: +:RECIPE_ID: 23167 +:CATEGORY: Lunch +:KEYWORDS: Chicken,Chicken and Rice,Lunch,One Pot,Rice +:CREATED: 2026-03-08T00:10:27+0000 +:MODIFIED: 2026-03-08T00:13:23+0000 +:URL: https://nextcloud.zainezq.com/apps/cookbook/webapp/recipes/23167 +:END: +#+ATTR_HTML: :class recipe-meta +| | | +|---|---| +| *Category* | Lunch | +| *Keywords* | Chicken,Chicken and Rice,Lunch,One Pot,Rice | +| *Created* | 2026-03-08 | +| *Modified* | 2026-03-08 | +| *Cooked* | /not yet made/ | +| *Link* | [[https://nextcloud.zainezq.com/apps/cookbook/webapp/recipes/23167][Open in Nextcloud]] | + +** Potato Peas Quesadilla :Lunch:Peas:Potato:Quesadilla: +:PROPERTIES: +:RECIPE_ID: 23144 +:CATEGORY: Lunch +:KEYWORDS: Lunch,Peas,Potato,Quesadilla +:CREATED: 2026-03-07T23:07:31+0000 +:MODIFIED: 2026-03-08T00:13:49+0000 +:URL: https://nextcloud.zainezq.com/apps/cookbook/webapp/recipes/23144 +:COOKED: <2025-07-18 Fri> +:END: +#+ATTR_HTML: :class recipe-meta +| | | +|---|---| +| *Category* | Lunch | +| *Keywords* | Lunch,Peas,Potato,Quesadilla | +| *Created* | 2026-03-07 | +| *Modified* | 2026-03-08 | +| *Cooked* | <2025-07-18 Fri> | +| *Link* | [[https://nextcloud.zainezq.com/apps/cookbook/webapp/recipes/23144][Open in Nextcloud]] | diff --git a/assets/scripts/cookbook.js b/assets/scripts/cookbook.js new file mode 100644 index 0000000..24f8ed1 --- /dev/null +++ b/assets/scripts/cookbook.js @@ -0,0 +1,232 @@ +/** + * cookbook.js + * Works both as a direct page load AND when injected as a stacked pane + * by script.js (which fetches pages and injects only #content). + */ + +(function () { + "use strict"; + + // ── Core init — scoped to a content root ────────────────────────────────── + + function initCookbook(root) { + // Already initialised on this root? + if (root.dataset.cbInit) return; + root.dataset.cbInit = "1"; + + const recipes = Array.from(root.querySelectorAll(".outline-3")) + .filter(el => el.querySelector("table.recipe-meta")); + + if (!recipes.length) return; + + // ── Helpers ───────────────────────────────────────────────────────────── + + function cellValue(table, label) { + for (const row of table.querySelectorAll("tbody tr")) { + const cells = row.querySelectorAll("td"); + if (cells.length >= 2 && cells[0].textContent.trim() === label) + return cells[1].textContent.trim(); + } + return ""; + } + + function isCooked(table) { + const v = cellValue(table, "Cooked"); + return Boolean(v && v !== "not yet made"); + } + + // ── Collect keywords ───────────────────────────────────────────────────── + + const allKeywords = new Set(); + recipes.forEach(block => { + const kw = cellValue(block.querySelector("table.recipe-meta"), "Keywords"); + if (kw) kw.split(",").forEach(k => allKeywords.add(k.trim())); + }); + + // ── Build toolbar ──────────────────────────────────────────────────────── + + const toolbar = document.createElement("div"); + toolbar.className = "cb-toolbar"; + toolbar.setAttribute("role", "search"); + toolbar.innerHTML = ` +
+ +
+ + + +
+
+ +
`; + + const firstSection = root.querySelector(".outline-2"); + if (firstSection) firstSection.parentNode.insertBefore(toolbar, firstSection); + + const searchInput = toolbar.querySelector(".cb-search"); + const filterBtns = toolbar.querySelectorAll(".cb-filter-btn"); + const tagsWrap = toolbar.querySelector(".cb-tags"); + const countEl = toolbar.querySelector(".cb-count"); + + // Keyword pills + Array.from(allKeywords).sort().forEach(kw => { + const btn = document.createElement("button"); + btn.className = "cb-tag"; + btn.textContent = kw; + btn.dataset.tag = kw; + btn.setAttribute("aria-pressed", "false"); + tagsWrap.appendChild(btn); + }); + + // ── State ──────────────────────────────────────────────────────────────── + + let activeFilter = "all"; + let activeTags = new Set(); + let query = ""; + + // ── Filter engine ──────────────────────────────────────────────────────── + + function applyFilters() { + let shown = 0; + + recipes.forEach(block => { + const table = block.querySelector("table.recipe-meta"); + const cooked = isCooked(table); + const title = (block.querySelector("h3")?.textContent ?? "").toLowerCase(); + const kwRaw = cellValue(table, "Keywords"); + const kwLow = kwRaw.toLowerCase(); + + if (activeFilter === "cooked" && !cooked) { hide(block); return; } + if (activeFilter === "uncooked" && cooked) { hide(block); return; } + + if (activeTags.size) { + const recipeKws = kwRaw.split(",").map(k => k.trim()); + if ([...activeTags].some(t => !recipeKws.includes(t))) { hide(block); return; } + } + + if (query && !title.includes(query) && !kwLow.includes(query)) { + hide(block); return; + } + + show(block); + shown++; + }); + + // Hide category headings with no visible recipes + root.querySelectorAll(".outline-2").forEach(section => { + const hasVisible = Array.from(section.querySelectorAll(".outline-3")) + .some(b => b.style.display !== "none"); + section.style.display = hasVisible ? "" : "none"; + }); + + countEl.textContent = + shown === recipes.length + ? `${shown} recipe${shown !== 1 ? "s" : ""}` + : `${shown} / ${recipes.length}`; + } + + function hide(el) { el.style.display = "none"; } + function show(el) { el.style.display = ""; } + + // ── Events ─────────────────────────────────────────────────────────────── + + searchInput.addEventListener("input", () => { + query = searchInput.value.trim().toLowerCase(); + applyFilters(); + }); + + filterBtns.forEach(btn => { + btn.addEventListener("click", () => { + filterBtns.forEach(b => b.classList.remove("active")); + btn.classList.add("active"); + activeFilter = btn.dataset.filter; + applyFilters(); + }); + }); + + tagsWrap.addEventListener("click", e => { + const btn = e.target.closest(".cb-tag"); + if (!btn) return; + const tag = btn.dataset.tag; + if (activeTags.has(tag)) { + activeTags.delete(tag); + btn.classList.remove("active"); + btn.setAttribute("aria-pressed", "false"); + } else { + activeTags.add(tag); + btn.classList.add("active"); + btn.setAttribute("aria-pressed", "true"); + } + applyFilters(); + }); + + // ── Badges ─────────────────────────────────────────────────────────────── + + recipes.forEach(block => { + const table = block.querySelector("table.recipe-meta"); + const h3 = block.querySelector("h3"); + const cooked = isCooked(table); + + block.classList.add(cooked ? "cb-cooked" : "cb-uncooked"); + + const badge = document.createElement("span"); + badge.className = "cb-badge " + (cooked ? "cb-badge-cooked" : "cb-badge-uncooked"); + badge.textContent = cooked ? "made" : "not yet"; + + const tagSpan = h3.querySelector(".tag"); + if (tagSpan) h3.insertBefore(badge, tagSpan); + else h3.appendChild(badge); + }); + + applyFilters(); + } + + // ── Entry points ────────────────────────────────────────────────────────── + + // 1. Direct page load — #content is already in the DOM + function tryInitOnContent() { + const content = document.getElementById("content"); + if (content && content.querySelector("table.recipe-meta")) { + initCookbook(content); + } + } + + // 2. Stacked pane — script.js fetches a page and appends a new
+ // with the #content inside it. Watch for that. + function watchForPanes() { + const track = document.querySelector(".stack-track"); + if (!track) return; + + const observer = new MutationObserver(mutations => { + mutations.forEach(m => { + m.addedNodes.forEach(node => { + if (node.nodeType !== 1) return; + // script.js appends an
+ // containing a div#content + const content = node.id === "content" + ? node + : node.querySelector("#content, .content"); + if (content && content.querySelector("table.recipe-meta")) { + initCookbook(content); + } + }); + }); + }); + + observer.observe(track, { childList: true, subtree: true }); + } + + // Run both + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => { + tryInitOnContent(); + watchForPanes(); + }); + } else { + // Already parsed (script has `defer` but DOM may already be ready) + tryInitOnContent(); + watchForPanes(); + } + +})(); diff --git a/assets/styles/cookbook.css b/assets/styles/cookbook.css new file mode 100644 index 0000000..aee87e5 --- /dev/null +++ b/assets/styles/cookbook.css @@ -0,0 +1,214 @@ +/* cookbook.css — minimal, breathes with the page */ + +/* ── Recipe meta table ───────────────────────────────────────────────────── */ + +table.recipe-meta { + border-collapse: collapse; + //font-size: 0.82em; + opacity: 1; + margin: 0.3em 0 1em; + width: auto; + //border: none; + background: transparent; +} + +table.recipe-meta thead { display: none; } + +table.recipe-meta td { + padding: 0.15em 1em 0.15em 0; + border: none !important; + vertical-align: top; + line-height: 1.5; + font-weight: 300; +} + +table.recipe-meta td:first-child { + //font-family: var(--font-mono); + //font-size: 0.75em; + letter-spacing: 0.05em; + text-transform: uppercase; + //color: var(--fg-faint); + white-space: nowrap; + min-width: 5em; + padding-top: 0.28em; +} + +table.recipe-meta td a { + color: var(--accent); + border-bottom: 1px solid var(--accent-dim); +} + +table.recipe-meta td a:hover { + color: var(--fg); + border-bottom-color: var(--fg-faint); +} + +table.recipe-meta .timestamp { + color: var(--accent); + font-family: var(--font-mono); +} + +/* ── Toolbar — no box, just spacing and a faint separator ───────────────── */ + +.cb-toolbar { + margin: 0 0 1.8em; + padding: 0; + background: transparent; + border: none; + border-bottom: 1px solid var(--border); + padding-bottom: 0.9em; +} + +.cb-toolbar-inner { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.4em 0.6em; +} + +/* ── Search ──────────────────────────────────────────────────────────────── */ + +.cb-search { + flex: 1 1 140px; + min-width: 0; + padding: 0.3em 0; + background: transparent; + color: var(--fg); + border: none; + border-bottom: 1px solid var(--border); + border-radius: 0; + font-size: 0.875rem; + font-family: var(--font-body); + font-weight: 300; + outline: none; + transition: border-color var(--dur-fast); +} + +.cb-search::placeholder { + color: var(--fg-faint); + font-style: italic; +} + +.cb-search:focus { + border-bottom-color: var(--accent); +} + +/* ── Status filters ──────────────────────────────────────────────────────── */ + +.cb-filters { + display: flex; + gap: 0.15em; +} + +.cb-filter-btn { + padding: 0.2em 0.55em; + background: transparent; + color: var(--fg-faint); + border: none; + border-radius: 3px; + font-size: 0.75rem; + font-family: var(--font-mono); + letter-spacing: 0.04em; + cursor: pointer; + transition: color var(--dur-fast), background var(--dur-fast); +} + +.cb-filter-btn:hover { + color: var(--fg); + background: var(--chip-bg); +} + +.cb-filter-btn.active { + color: var(--accent); + background: var(--accent-subtle); +} + +/* ── Keyword pills ───────────────────────────────────────────────────────── */ + +.cb-tags { + display: flex; + flex-wrap: wrap; + gap: 0.2em; + flex: 1 1 100%; +} + +.cb-tag { + padding: 0.12em 0.5em; + background: transparent; + color: var(--fg-faint); + border: 1px solid var(--border); + border-radius: 99px; + font-size: 0.7rem; + font-family: var(--font-mono); + letter-spacing: 0.03em; + cursor: pointer; + transition: color var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast); +} + +.cb-tag:hover { + color: var(--accent); + border-color: var(--accent-dim); +} + +.cb-tag.active { + color: var(--accent); + border-color: var(--accent-dim); + background: var(--accent-subtle); +} + +/* ── Count ───────────────────────────────────────────────────────────────── */ + +.cb-count { + font-size: 0.7rem; + font-family: var(--font-mono); + color: var(--fg-faint); + letter-spacing: 0.04em; + margin-left: auto; +} + +/* ── Badges ──────────────────────────────────────────────────────────────── */ + +.cb-badge { + display: inline-block; + padding: 0.1em 0.4em; + border-radius: 3px; + font-size: 0.62em; + font-family: var(--font-mono); + font-weight: 400; + vertical-align: middle; + margin-right: 0.3em; + letter-spacing: 0.05em; + text-transform: lowercase; + border: 1px solid; + line-height: 1.5; +} + +.cb-badge-cooked { + color: #6fcf97 !important; + border-color: #2d6a4f !important; + background: color-mix(in oklab, #2d6a4f 15%, transparent) !important; +} + +.cb-badge-uncooked { + color: var(--fg-faint) !important; + border-color: var(--border) !important; + background: transparent !important; +} + +/* ── Recipe block hover ──────────────────────────────────────────────────── */ + +.outline-3 { + border-left: 2px solid transparent; + padding-left: 0.6em; + transition: border-color var(--dur-mid); +} + +.outline-3:hover { + border-left-color: var(--accent-dim); +} + +/* ── Responsive ──────────────────────────────────────────────────────────── */ + +@media (max-width: 600px) { + .cb-count { margin-left: 0; } +} diff --git a/assets/styles/style.css b/assets/styles/style.css index d13651e..5128e9c 100755 --- a/assets/styles/style.css +++ b/assets/styles/style.css @@ -984,7 +984,8 @@ body.pane-fullscreen #content.content { max-width: 860px; } ========================================================= */ .hidden { display: none !important; } - +.recipe-meta td:first-child { font-weight: 600; white-space: nowrap; } +.recipe-meta { font-size: 0.9em; opacity: 0.85; } /* ========================================================= RESPONSIVE ========================================================= */ diff --git a/lisp/build.el b/lisp/build.el index be5f151..44a7d7f 100755 --- a/lisp/build.el +++ b/lisp/build.el @@ -145,12 +145,14 @@ + + " ) diff --git a/orgroam-build.log b/orgroam-build.log index 62c737b..7a845c2 100644 --- a/orgroam-build.log +++ b/orgroam-build.log @@ -9,5 +9,5 @@ Generating the JSON output... Building project (full rebuild with search index)... emacs -Q --script lisp/build.el Loading /home/zaine/master-folder/org_files/org_roam/lisp/macros.el (source)... -Starting full rebuild at 2026-03-08 13:37:37 -✅ Full rebuild complete in 6.37 seconds +Starting full rebuild at 2026-03-08 14:53:23 +✅ Full rebuild complete in 6.40 seconds