feat: created recipes structure
This commit is contained in:
parent
a035c08249
commit
f93207b4e3
7 changed files with 432 additions and 13 deletions
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}/`;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue