diff --git a/_includes/recipe.njk b/_includes/recipe.njk deleted file mode 100644 index aab5197..0000000 --- a/_includes/recipe.njk +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: base.njk ---- -
-
-

{{ title }}

- {% if isDraft %} - Draft - {% endif %} -

Version {{ recipeVersion }}

-
- -
- {{ content | safe }} -
- - {% set recipeTags = page.inputPath | extractTagsFromFile %} - - {% if recipeTags.length > 0 %} -
-

Tags

- -
- {% endif %} - - {% if not isDraft and isNewestVersion %} - - {% 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 %} - - {% endif %} -
\ No newline at end of file diff --git a/css/style.css b/css/style.css index 1784a63..0aa655a 100644 --- a/css/style.css +++ b/css/style.css @@ -64,28 +64,6 @@ a { 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 */ .post-list { list-style: none; diff --git a/eleventy.config.js b/eleventy.config.js index d24ef5c..cc45552 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -208,6 +208,12 @@ module.exports = (eleventyConfig) => { 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) => { const posts = collectionApi.getFilteredByGlob("posts/**/*.md").filter(isReleased); const tagMap = {}; @@ -218,7 +224,6 @@ module.exports = (eleventyConfig) => { tagMap[tag] = { name: tag, posts: [post, ...(tagMap[tag]?.posts ?? [])], - recipes: tagMap[tag]?.recipes ?? [], } }) }); @@ -234,109 +239,6 @@ module.exports = (eleventyConfig) => { 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 eleventyConfig.addFilter("fileHash", (file, dir = "css") => { const hash = getFileHash(file, dir); @@ -397,6 +299,7 @@ module.exports = (eleventyConfig) => { return { dir: { + input: ".", includes: "_includes", output: "_site" }, diff --git a/index.njk b/index.njk index 71793b8..bc952c7 100644 --- a/index.njk +++ b/index.njk @@ -8,17 +8,10 @@ description: Welcome to my website! I write about tech, politics, food, and hobb

{{ description }}

-{% if collections.posts.length > 0 %} -
-

Blog Posts

- {% if collections.posts.length > 3 %} - view all - {% endif %} -
+

Blog Posts

-{% endif %} -{% set recipesList = [] %} -{% for slug, recipeData in collections.recipesBySlug %} - {% if recipeData.newest %} - {% set recipesList = recipesList.concat([{slug: slug, data: recipeData}]) %} - {% endif %} -{% endfor %} - -{% if recipesList.length > 0 %} -
-

Recipes

- {% if recipesList.length > 3 %} - view all - {% endif %} -
- - +{% if collections.posts.length == 0 %} +

No blog posts yet.

{% endif %} \ No newline at end of file diff --git a/posts-index.njk b/posts-index.njk deleted file mode 100644 index abb3d19..0000000 --- a/posts-index.njk +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: base.njk -title: All Blog Posts -description: All blog posts on Volpe. -permalink: /posts/ ---- - -

Blog Posts

- -{% if collections.posts.length > 0 %} - -{% else %} -

No blog posts yet.

-{% endif %} \ No newline at end of file diff --git a/posts/posts.11tydata.js b/posts/posts.11tydata.js index 4d8d3bd..fcec690 100644 --- a/posts/posts.11tydata.js +++ b/posts/posts.11tydata.js @@ -42,13 +42,6 @@ const getTitleFromFilename = (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 { const stats = fs.statSync(filePath); const time = stats.birthtime ?? stats.mtime; diff --git a/recipe.njk b/recipe.njk deleted file mode 100644 index 93ce1a2..0000000 --- a/recipe.njk +++ /dev/null @@ -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 %} -
-
-

{{ recipe.data.title }}

-
- -
- {{ recipe.content | safe }} -
- - {% set recipeTags = recipe.inputPath | extractTagsFromFile %} - - {% if recipeTags.length > 0 %} -
-

Tags

- -
- {% 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 %} - - {% endif %} -
-{% else %} -

No published recipe found for this slug.

-{% endif %} \ No newline at end of file diff --git a/recipes-index.njk b/recipes-index.njk deleted file mode 100644 index b129f8c..0000000 --- a/recipes-index.njk +++ /dev/null @@ -1,34 +0,0 @@ ---- -layout: base.njk -title: All Recipes -description: All recipes on Volpe. -permalink: /recipes/ ---- - -

Recipes

- -{% set hasRecipes = false %} -{% for slug, recipeData in collections.recipesBySlug %} - {% if recipeData.newest %} - {% set hasRecipes = true %} - {% endif %} -{% endfor %} - -{% if hasRecipes %} - -{% else %} -

No recipes yet.

-{% endif %} \ No newline at end of file diff --git a/recipes/recipes.11tydata.js b/recipes/recipes.11tydata.js deleted file mode 100644 index 9c6a9d2..0000000 --- a/recipes/recipes.11tydata.js +++ /dev/null @@ -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}/`; - } - } -} \ No newline at end of file diff --git a/tags.njk b/tags.njk index fad82f2..e0a18b4 100644 --- a/tags.njk +++ b/tags.njk @@ -1,6 +1,6 @@ --- pagination: - data: collections.contentByTag + data: collections.postsByTag size: 1 alias: tag addAllPagesToCollections: true @@ -8,14 +8,10 @@ permalink: /tags/{{ tag }}/ layout: base.njk --- -

Content tagged "#{{ collections.contentByTag[tag].name }}"

+

Posts tagged "#{{ collections.postsByTag[tag].name }}"

-{% set tagData = collections.contentByTag[tag] %} - -{% if tagData.posts.length > 0 %} -

Posts

-{% endif %} -{% if tagData.recipes.length > 0 %} -

Recipes

-
-{% endif %} - -

← All tags

\ No newline at end of file +

� All tags

\ No newline at end of file