Compare commits
No commits in common. "0d5bb627753c60c57a837ec9b96a8367f7a4342c" and "a035c082499f0490727cfef5bb439bb23bf93da6" have entirely different histories.
0d5bb62775
...
a035c08249
10 changed files with 14 additions and 527 deletions
|
|
@ -1,55 +0,0 @@
|
||||||
---
|
|
||||||
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>
|
|
||||||
|
|
@ -64,28 +64,6 @@ a {
|
||||||
color: var(--secondary-color);
|
color: var(--secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Section header with view all link */
|
|
||||||
.section-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header h1 {
|
|
||||||
margin: 1.5rem 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-all {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--secondary-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-all:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Blog post list */
|
/* Blog post list */
|
||||||
.post-list {
|
.post-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,12 @@ 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 = {};
|
||||||
|
|
@ -218,7 +224,6 @@ 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 ?? [],
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -234,109 +239,6 @@ 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);
|
||||||
|
|
@ -397,6 +299,7 @@ module.exports = (eleventyConfig) => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dir: {
|
dir: {
|
||||||
|
input: ".",
|
||||||
includes: "_includes",
|
includes: "_includes",
|
||||||
output: "_site"
|
output: "_site"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
42
index.njk
42
index.njk
|
|
@ -8,17 +8,10 @@ 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>
|
||||||
<div class="section-header">
|
|
||||||
<h1>Blog Posts</h1>
|
|
||||||
{% if collections.posts.length > 3 %}
|
|
||||||
<a href="/posts/" class="view-all">view all</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="post-list">
|
<ul class="post-list">
|
||||||
{% for post in collections.posts %}
|
{% for post in collections.posts %}
|
||||||
{% if loop.index0 < 3 %}
|
|
||||||
<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>
|
||||||
|
|
@ -28,38 +21,9 @@ description: Welcome to my website! I write about tech, politics, food, and hobb
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% set recipesList = [] %}
|
{% if collections.posts.length == 0 %}
|
||||||
{% for slug, recipeData in collections.recipesBySlug %}
|
<p>No blog posts yet.</p>
|
||||||
{% if recipeData.newest %}
|
|
||||||
{% set recipesList = recipesList.concat([{slug: slug, data: recipeData}]) %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if recipesList.length > 0 %}
|
|
||||||
<div class="section-header">
|
|
||||||
<h1>Recipes</h1>
|
|
||||||
{% if recipesList.length > 3 %}
|
|
||||||
<a href="/recipes/" class="view-all">view all</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="post-list">
|
|
||||||
{% for recipe in recipesList %}
|
|
||||||
{% if loop.index0 < 3 %}
|
|
||||||
<li>
|
|
||||||
<a href="/recipe/{{ recipe.slug }}/" class="post-card">
|
|
||||||
<h2>{{ recipe.data.newest.data.title }}</h2>
|
|
||||||
{% if recipe.data.newest.data.description %}
|
|
||||||
<p>{{ recipe.data.newest.data.description }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
---
|
|
||||||
layout: base.njk
|
|
||||||
title: All Blog Posts
|
|
||||||
description: All blog posts on Volpe.
|
|
||||||
permalink: /posts/
|
|
||||||
---
|
|
||||||
|
|
||||||
<h1>Blog Posts</h1>
|
|
||||||
|
|
||||||
{% if collections.posts.length > 0 %}
|
|
||||||
<ul class="post-list">
|
|
||||||
{% for post in collections.posts %}
|
|
||||||
<li>
|
|
||||||
<a href="{{ post.url }}" class="post-card">
|
|
||||||
<h2>{{ post.data.title }}</h2>
|
|
||||||
<time datetime="{{ post.data.createdAt | dateTimeFormat }}">{{ post.data.createdAt | dateTimeFormat }}</time>
|
|
||||||
{% if post.data.description %}
|
|
||||||
<p>{{ post.data.description }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% else %}
|
|
||||||
<p>No blog posts yet.</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
@ -42,13 +42,6 @@ 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
61
recipe.njk
|
|
@ -1,61 +0,0 @@
|
||||||
---
|
|
||||||
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 %}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
---
|
|
||||||
layout: base.njk
|
|
||||||
title: All Recipes
|
|
||||||
description: All recipes on Volpe.
|
|
||||||
permalink: /recipes/
|
|
||||||
---
|
|
||||||
|
|
||||||
<h1>Recipes</h1>
|
|
||||||
|
|
||||||
{% set hasRecipes = false %}
|
|
||||||
{% for slug, recipeData in collections.recipesBySlug %}
|
|
||||||
{% if recipeData.newest %}
|
|
||||||
{% set hasRecipes = true %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if hasRecipes %}
|
|
||||||
<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>
|
|
||||||
{% else %}
|
|
||||||
<p>No recipes yet.</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
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.contentByTag
|
data: collections.postsByTag
|
||||||
size: 1
|
size: 1
|
||||||
alias: tag
|
alias: tag
|
||||||
addAllPagesToCollections: true
|
addAllPagesToCollections: true
|
||||||
|
|
@ -8,14 +8,10 @@ permalink: /tags/{{ tag }}/
|
||||||
layout: base.njk
|
layout: base.njk
|
||||||
---
|
---
|
||||||
|
|
||||||
<h1>Content tagged "#{{ collections.contentByTag[tag].name }}"</h1>
|
<h1>Posts tagged "#{{ collections.postsByTag[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 tagData.posts %}
|
{% for post in collections.postsByTag[tag].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>
|
||||||
|
|
@ -27,22 +23,5 @@ layout: base.njk
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if tagData.recipes.length > 0 %}
|
<p><a href="/tags/"><3E> All tags</a></p>
|
||||||
<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