feat: created recipes structure
This commit is contained in:
parent
a035c08249
commit
f93207b4e3
7 changed files with 432 additions and 13 deletions
55
_includes/recipe.njk
Normal file
55
_includes/recipe.njk
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
---
|
||||||
|
layout: base.njk
|
||||||
|
---
|
||||||
|
<article class="recipe">
|
||||||
|
<header class="recipe-header">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
{% if isDraft %}
|
||||||
|
<span class="draft-badge">Draft</span>
|
||||||
|
{% endif %}
|
||||||
|
<p class="recipe-version">Version {{ recipeVersion }}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="recipe-content">
|
||||||
|
{{ content | safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% set recipeTags = page.inputPath | extractTagsFromFile %}
|
||||||
|
|
||||||
|
{% if recipeTags.length > 0 %}
|
||||||
|
<section class="recipe-tags">
|
||||||
|
<h2>Tags</h2>
|
||||||
|
<ul class="tag-list">
|
||||||
|
{% for tag in recipeTags %}
|
||||||
|
<li><a href="/tags/{{ tag | lower }}/">#{{ tag }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not isDraft and isNewestVersion %}
|
||||||
|
<p class="recipe-permalink">
|
||||||
|
Permalink: <a href="/recipe/{{ recipeSlug }}/">/recipe/{{ recipeSlug }}/</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% set recipeData = collections.recipesBySlug[recipeSlug] %}
|
||||||
|
{% set nonDraftVersions = [] %}
|
||||||
|
{% for version in recipeData.versions %}
|
||||||
|
{% if not version.data.isDraft %}
|
||||||
|
{% set nonDraftVersions = (nonDraftVersions.push(version), nonDraftVersions) %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if nonDraftVersions.length > 1 %}
|
||||||
|
<aside class="recipe-other-versions">
|
||||||
|
<p>Other versions:
|
||||||
|
{% for version in nonDraftVersions %}
|
||||||
|
{% if version.url != page.url %}
|
||||||
|
<a href="{{ version.url }}">v{{ version.data.recipeVersion }}</a>{% if not loop.last %}, {% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
{% endif %}
|
||||||
|
</article>
|
||||||
|
|
@ -208,12 +208,6 @@ module.exports = (eleventyConfig) => {
|
||||||
return grouped;
|
return grouped;
|
||||||
});
|
});
|
||||||
|
|
||||||
eleventyConfig.addCollection("contentTags", (collectionApi) => {
|
|
||||||
const posts = collectionApi.getFilteredByGlob("posts/**/*.md").filter(isReleased);
|
|
||||||
|
|
||||||
return [...new Set(posts.flatMap(post => getPostTags(post, md)))].sort();
|
|
||||||
});
|
|
||||||
|
|
||||||
eleventyConfig.addCollection("postsByTag", (collectionApi) => {
|
eleventyConfig.addCollection("postsByTag", (collectionApi) => {
|
||||||
const posts = collectionApi.getFilteredByGlob("posts/**/*.md").filter(isReleased);
|
const posts = collectionApi.getFilteredByGlob("posts/**/*.md").filter(isReleased);
|
||||||
const tagMap = {};
|
const tagMap = {};
|
||||||
|
|
@ -224,6 +218,7 @@ module.exports = (eleventyConfig) => {
|
||||||
tagMap[tag] = {
|
tagMap[tag] = {
|
||||||
name: tag,
|
name: tag,
|
||||||
posts: [post, ...(tagMap[tag]?.posts ?? [])],
|
posts: [post, ...(tagMap[tag]?.posts ?? [])],
|
||||||
|
recipes: tagMap[tag]?.recipes ?? [],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -239,6 +234,109 @@ module.exports = (eleventyConfig) => {
|
||||||
return tagMap;
|
return tagMap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Recipe collections
|
||||||
|
eleventyConfig.addCollection("recipes", (collectionApi) => {
|
||||||
|
return collectionApi.getFilteredByGlob("recipes/**/*.md")
|
||||||
|
.filter(recipe => recipe.data.draft !== true);
|
||||||
|
});
|
||||||
|
|
||||||
|
eleventyConfig.addCollection("allRecipes", (collectionApi) => {
|
||||||
|
return collectionApi.getFilteredByGlob("recipes/**/*.md");
|
||||||
|
});
|
||||||
|
|
||||||
|
eleventyConfig.addCollection("recipesBySlug", (collectionApi) => {
|
||||||
|
const recipes = collectionApi.getFilteredByGlob("recipes/**/*.md");
|
||||||
|
|
||||||
|
// Group recipes by slug using reduce
|
||||||
|
const grouped = recipes.reduce((acc, recipe) => {
|
||||||
|
const slug = recipe.data.recipeSlug;
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[slug]: [...(acc[slug] || []), recipe],
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Transform grouped recipes into final structure with sorted versions and newest non-draft
|
||||||
|
return Object.entries(grouped).reduce((acc, [slug, recipeList]) => {
|
||||||
|
const versions = [...recipeList].sort((a, b) => a.data.recipeVersion - b.data.recipeVersion);
|
||||||
|
const newest = [...versions].reverse().find(r => r.data.draft !== true) || null;
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[slug]: { versions, newest },
|
||||||
|
};
|
||||||
|
}, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get tags from recipes (only from newest non-draft versions)
|
||||||
|
const getRecipeTags = (recipe) => {
|
||||||
|
const filePath = recipe.inputPath;
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
const tags = extractTags(content, md);
|
||||||
|
return tags.map(tag => tag.toLowerCase());
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
eleventyConfig.addCollection("contentTags", (collectionApi) => {
|
||||||
|
const posts = collectionApi.getFilteredByGlob("posts/**/*.md").filter(isReleased);
|
||||||
|
const recipes = collectionApi.getFilteredByGlob("recipes/**/*.md")
|
||||||
|
.filter(r => r.data.isNewestVersion && r.data.draft !== true);
|
||||||
|
|
||||||
|
const postTags = posts.flatMap(post => getPostTags(post, md));
|
||||||
|
const recipeTags = recipes.flatMap(recipe => getRecipeTags(recipe));
|
||||||
|
|
||||||
|
return [...new Set([...postTags, ...recipeTags])].sort();
|
||||||
|
});
|
||||||
|
|
||||||
|
eleventyConfig.addCollection("contentByTag", (collectionApi) => {
|
||||||
|
const posts = collectionApi.getFilteredByGlob("posts/**/*.md").filter(isReleased);
|
||||||
|
const recipes = collectionApi.getFilteredByGlob("recipes/**/*.md")
|
||||||
|
.filter(r => r.data.isNewestVersion && r.data.draft !== true);
|
||||||
|
|
||||||
|
const sortByDate = (a, b) => {
|
||||||
|
const aDate = a.data.createdAt || a.date;
|
||||||
|
const bDate = b.data.createdAt || b.date;
|
||||||
|
return aDate - bDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build tag map from posts
|
||||||
|
const postTagMap = posts.reduce((acc, post) => {
|
||||||
|
const tags = getPostTags(post, md);
|
||||||
|
return tags.reduce((innerAcc, tag) => ({
|
||||||
|
...innerAcc,
|
||||||
|
[tag]: {
|
||||||
|
name: tag,
|
||||||
|
posts: [...(innerAcc[tag]?.posts || []), post],
|
||||||
|
recipes: innerAcc[tag]?.recipes || [],
|
||||||
|
},
|
||||||
|
}), acc);
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Merge recipe tags into the tag map
|
||||||
|
const tagMap = recipes.reduce((acc, recipe) => {
|
||||||
|
const tags = getRecipeTags(recipe);
|
||||||
|
return tags.reduce((innerAcc, tag) => ({
|
||||||
|
...innerAcc,
|
||||||
|
[tag]: {
|
||||||
|
name: tag,
|
||||||
|
posts: innerAcc[tag]?.posts || [],
|
||||||
|
recipes: [...(innerAcc[tag]?.recipes || []), recipe],
|
||||||
|
},
|
||||||
|
}), acc);
|
||||||
|
}, postTagMap);
|
||||||
|
|
||||||
|
// Return with sorted posts
|
||||||
|
return Object.entries(tagMap).reduce((acc, [tag, tagData]) => ({
|
||||||
|
...acc,
|
||||||
|
[tag]: {
|
||||||
|
...tagData,
|
||||||
|
posts: [...tagData.posts].sort(sortByDate),
|
||||||
|
},
|
||||||
|
}), {});
|
||||||
|
});
|
||||||
|
|
||||||
// Cache busting filter: returns hashed filename
|
// Cache busting filter: returns hashed filename
|
||||||
eleventyConfig.addFilter("fileHash", (file, dir = "css") => {
|
eleventyConfig.addFilter("fileHash", (file, dir = "css") => {
|
||||||
const hash = getFileHash(file, dir);
|
const hash = getFileHash(file, dir);
|
||||||
|
|
@ -299,7 +397,6 @@ module.exports = (eleventyConfig) => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dir: {
|
dir: {
|
||||||
input: ".",
|
|
||||||
includes: "_includes",
|
includes: "_includes",
|
||||||
output: "_site"
|
output: "_site"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
28
index.njk
28
index.njk
|
|
@ -8,6 +8,7 @@ description: Welcome to my website! I write about tech, politics, food, and hobb
|
||||||
<p>{{ description }}</p>
|
<p>{{ description }}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{% if collections.posts.length > 0 %}
|
||||||
<h1>Blog Posts</h1>
|
<h1>Blog Posts</h1>
|
||||||
|
|
||||||
<ul class="post-list">
|
<ul class="post-list">
|
||||||
|
|
@ -23,7 +24,30 @@ description: Welcome to my website! I write about tech, politics, food, and hobb
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if collections.posts.length == 0 %}
|
{% set hasRecipes = false %}
|
||||||
<p>No blog posts yet.</p>
|
{% for slug, recipeData in collections.recipesBySlug %}
|
||||||
|
{% if recipeData.newest %}
|
||||||
|
{% set hasRecipes = true %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if hasRecipes %}
|
||||||
|
<h1>Recipes</h1>
|
||||||
|
|
||||||
|
<ul class="post-list">
|
||||||
|
{% for slug, recipeData in collections.recipesBySlug %}
|
||||||
|
{% if recipeData.newest %}
|
||||||
|
<li>
|
||||||
|
<a href="/recipe/{{ slug }}/" class="post-card">
|
||||||
|
<h2>{{ recipeData.newest.data.title }}</h2>
|
||||||
|
{% if recipeData.newest.data.description %}
|
||||||
|
<p>{{ recipeData.newest.data.description }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
@ -42,6 +42,13 @@ const getTitleFromFilename = (filePath) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFileCreatedTime = (filePath) => {
|
const getFileCreatedTime = (filePath) => {
|
||||||
|
// Try git first for accurate cross-system creation time
|
||||||
|
const gitTime = getGitCreatedTime(filePath);
|
||||||
|
if (gitTime) {
|
||||||
|
return gitTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to filesystem for untracked files
|
||||||
try {
|
try {
|
||||||
const stats = fs.statSync(filePath);
|
const stats = fs.statSync(filePath);
|
||||||
const time = stats.birthtime ?? stats.mtime;
|
const time = stats.birthtime ?? stats.mtime;
|
||||||
|
|
|
||||||
61
recipe.njk
Normal file
61
recipe.njk
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
---
|
||||||
|
pagination:
|
||||||
|
data: collections.recipesBySlug
|
||||||
|
size: 1
|
||||||
|
alias: slugData
|
||||||
|
resolve: keys
|
||||||
|
permalink: /recipe/{{ slugData }}/
|
||||||
|
layout: base.njk
|
||||||
|
excludeFromSitemap: true
|
||||||
|
eleventyComputed:
|
||||||
|
title: "{{ collections.recipesBySlug[slugData].newest.data.title }}"
|
||||||
|
---
|
||||||
|
|
||||||
|
{% set recipeData = collections.recipesBySlug[slugData] %}
|
||||||
|
{% set recipe = recipeData.newest %}
|
||||||
|
|
||||||
|
{% if recipe %}
|
||||||
|
<article class="recipe">
|
||||||
|
<header class="recipe-header">
|
||||||
|
<h1>{{ recipe.data.title }}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="recipe-content">
|
||||||
|
{{ recipe.content | safe }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% set recipeTags = recipe.inputPath | extractTagsFromFile %}
|
||||||
|
|
||||||
|
{% if recipeTags.length > 0 %}
|
||||||
|
<section class="recipe-tags">
|
||||||
|
<h2>Tags</h2>
|
||||||
|
<ul class="tag-list">
|
||||||
|
{% for tag in recipeTags %}
|
||||||
|
<li><a href="/tags/{{ tag | lower }}/">#{{ tag }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% set nonDraftVersions = [] %}
|
||||||
|
{% for version in recipeData.versions %}
|
||||||
|
{% if not version.data.isDraft %}
|
||||||
|
{% set nonDraftVersions = (nonDraftVersions.push(version), nonDraftVersions) %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if nonDraftVersions.length > 1 %}
|
||||||
|
<aside class="recipe-other-versions">
|
||||||
|
<p>Other versions:
|
||||||
|
{% for version in nonDraftVersions %}
|
||||||
|
{% if not version.data.isNewestVersion %}
|
||||||
|
<a href="{{ version.url }}">v{{ version.data.recipeVersion }}</a>{% if not loop.last %}, {% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
{% endif %}
|
||||||
|
</article>
|
||||||
|
{% else %}
|
||||||
|
<p>No published recipe found for this slug.</p>
|
||||||
|
{% endif %}
|
||||||
154
recipes/recipes.11tydata.js
Normal file
154
recipes/recipes.11tydata.js
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require("path");
|
||||||
|
const { execSync } = require("child_process");
|
||||||
|
|
||||||
|
const getSlugFromPath = (filePath) => {
|
||||||
|
// Normalize the path - remove leading ./ if present
|
||||||
|
const normalizedPath = filePath.startsWith('./') ? filePath.slice(2) : filePath;
|
||||||
|
|
||||||
|
// For top-level files: recipes/foo.md -> slug is "foo"
|
||||||
|
// For nested folders: recipes/bar/v1.md -> slug is "bar"
|
||||||
|
const parts = normalizedPath.split(path.sep);
|
||||||
|
|
||||||
|
// parts[0] should be 'recipes', parts[1] is the key part
|
||||||
|
if (parts.length >= 2 && parts[0] === 'recipes') {
|
||||||
|
const secondPart = parts[1];
|
||||||
|
// If it's a .md file at the top level (recipes/foo.md), strip the extension
|
||||||
|
if (secondPart.endsWith('.md')) {
|
||||||
|
return path.basename(secondPart, '.md');
|
||||||
|
}
|
||||||
|
// Otherwise it's a folder name (recipes/bar/v1.md -> bar)
|
||||||
|
return secondPart;
|
||||||
|
}
|
||||||
|
return path.basename(filePath, '.md');
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTitleFromSlug = (slug) => {
|
||||||
|
return slug
|
||||||
|
.split('-')
|
||||||
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
const getGitCreatedTime = (filePath) => {
|
||||||
|
try {
|
||||||
|
const result = execSync(
|
||||||
|
`git log --diff-filter=A --follow --format=%aI -- "${filePath}" | tail -1`,
|
||||||
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
||||||
|
).trim();
|
||||||
|
if (result) {
|
||||||
|
return new Date(result).getTime();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Git command failed, fall through to filesystem
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFileCreatedTime = (filePath) => {
|
||||||
|
// Try git first for accurate cross-system creation time
|
||||||
|
const gitTime = getGitCreatedTime(filePath);
|
||||||
|
if (gitTime) {
|
||||||
|
return gitTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to filesystem for untracked files
|
||||||
|
try {
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
const time = stats.birthtime ?? stats.mtime;
|
||||||
|
return time.getTime();
|
||||||
|
} catch (e) {
|
||||||
|
return Date.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVersion = (filePath) => {
|
||||||
|
const dirName = path.dirname(filePath)
|
||||||
|
|
||||||
|
// Top-level files (directly in recipes/) always have version 0
|
||||||
|
if (dirName === 'recipes' || dirName === './recipes') {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(dirName).filter(f => f.endsWith('.md'))
|
||||||
|
|
||||||
|
const filesWithDates = files
|
||||||
|
.map((file) => {
|
||||||
|
const fullPath = path.join(dirName, file);
|
||||||
|
return {
|
||||||
|
file: fullPath,
|
||||||
|
createdAt: getFileCreatedTime(fullPath),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.createdAt - b.createdAt) // oldest first (version 0)
|
||||||
|
|
||||||
|
const normalizedFilePath = filePath.startsWith('./') ? filePath.slice(2) : filePath;
|
||||||
|
const version = filesWithDates.findIndex(({ file }) => {
|
||||||
|
const normalizedFile = file.startsWith('./') ? file.slice(2) : file;
|
||||||
|
return normalizedFile === normalizedFilePath;
|
||||||
|
});
|
||||||
|
|
||||||
|
return version === -1 ? 0 : version;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNewestVersion = (filePath) => {
|
||||||
|
const dirName = path.dirname(filePath)
|
||||||
|
|
||||||
|
// Top-level files are always the "newest" (only version)
|
||||||
|
if (dirName === 'recipes' || dirName === './recipes') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(dirName).filter(f => f.endsWith('.md'))
|
||||||
|
|
||||||
|
const filesWithDates = files
|
||||||
|
.map((file) => {
|
||||||
|
const fullPath = path.join(dirName, file);
|
||||||
|
return {
|
||||||
|
file: fullPath,
|
||||||
|
createdAt: getFileCreatedTime(fullPath),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.createdAt - a.createdAt) // newest first
|
||||||
|
|
||||||
|
const normalizedFilePath = filePath.startsWith('./') ? filePath.slice(2) : filePath;
|
||||||
|
const newestFile = filesWithDates[0]?.file;
|
||||||
|
const normalizedNewest = newestFile?.startsWith('./') ? newestFile.slice(2) : newestFile;
|
||||||
|
|
||||||
|
return normalizedFilePath === normalizedNewest;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
layout: "recipe.njk",
|
||||||
|
tags: ["recipe"],
|
||||||
|
eleventyComputed: {
|
||||||
|
recipeSlug: (data) => {
|
||||||
|
return getSlugFromPath(data.page.inputPath);
|
||||||
|
},
|
||||||
|
title: (data) => {
|
||||||
|
if (data.title && data.title !== data.page.fileSlug) {
|
||||||
|
return data.title;
|
||||||
|
}
|
||||||
|
const slug = getSlugFromPath(data.page.inputPath);
|
||||||
|
return getTitleFromSlug(slug);
|
||||||
|
},
|
||||||
|
recipeVersion: (data) => {
|
||||||
|
return getVersion(data.page.inputPath);
|
||||||
|
},
|
||||||
|
isNewestVersion: (data) => {
|
||||||
|
// Draft recipes are never considered "newest" for redirect purposes
|
||||||
|
if (data.draft === true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isNewestVersion(data.page.inputPath);
|
||||||
|
},
|
||||||
|
isDraft: (data) => {
|
||||||
|
return data.draft === true;
|
||||||
|
},
|
||||||
|
permalink: (data) => {
|
||||||
|
const slug = getSlugFromPath(data.page.inputPath);
|
||||||
|
const version = getVersion(data.page.inputPath);
|
||||||
|
return `/recipe/${slug}/${version}/`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
tags.njk
29
tags.njk
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
pagination:
|
pagination:
|
||||||
data: collections.postsByTag
|
data: collections.contentByTag
|
||||||
size: 1
|
size: 1
|
||||||
alias: tag
|
alias: tag
|
||||||
addAllPagesToCollections: true
|
addAllPagesToCollections: true
|
||||||
|
|
@ -8,10 +8,14 @@ permalink: /tags/{{ tag }}/
|
||||||
layout: base.njk
|
layout: base.njk
|
||||||
---
|
---
|
||||||
|
|
||||||
<h1>Posts tagged "#{{ collections.postsByTag[tag].name }}"</h1>
|
<h1>Content tagged "#{{ collections.contentByTag[tag].name }}"</h1>
|
||||||
|
|
||||||
|
{% set tagData = collections.contentByTag[tag] %}
|
||||||
|
|
||||||
|
{% if tagData.posts.length > 0 %}
|
||||||
|
<h2>Posts</h2>
|
||||||
<ul class="post-list">
|
<ul class="post-list">
|
||||||
{% for post in collections.postsByTag[tag].posts %}
|
{% for post in tagData.posts %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ post.url }}" class="post-card">
|
<a href="{{ post.url }}" class="post-card">
|
||||||
<h2>{{ post.data.title }}</h2>
|
<h2>{{ post.data.title }}</h2>
|
||||||
|
|
@ -23,5 +27,22 @@ layout: base.njk
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p><a href="/tags/"><3E> All tags</a></p>
|
{% if tagData.recipes.length > 0 %}
|
||||||
|
<h2>Recipes</h2>
|
||||||
|
<ul class="post-list">
|
||||||
|
{% for recipe in tagData.recipes %}
|
||||||
|
<li>
|
||||||
|
<a href="/recipe/{{ recipe.data.recipeSlug }}/" class="post-card">
|
||||||
|
<h2>{{ recipe.data.title }}</h2>
|
||||||
|
{% if recipe.data.description %}
|
||||||
|
<p>{{ recipe.data.description }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<p><a href="/tags/">← All tags</a></p>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue