feat(listings): hide unlisted posts from blog and notes

This commit is contained in:
svemagie
2026-03-08 16:52:54 +01:00
parent b0c68dc375
commit 182d0fd26e
6 changed files with 65 additions and 31 deletions

View File

@@ -3,7 +3,7 @@ layout: layouts/base.njk
title: Blog title: Blog
withSidebar: true withSidebar: true
pagination: pagination:
data: collections.posts data: collections.listedPosts
size: 20 size: 20
alias: paginatedPosts alias: paginatedPosts
permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}" permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
@@ -11,19 +11,19 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
<div class="h-feed"> <div class="h-feed">
<div class="flex flex-wrap items-center gap-4 mb-2"> <div class="flex flex-wrap items-center gap-4 mb-2">
<h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100">Blog</h1> <h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100">Blog</h1>
{% set sparklineSvg = collections.posts | postingFrequency %} {% set sparklineSvg = collections.listedPosts | postingFrequency %}
{% if sparklineSvg %} {% if sparklineSvg %}
<div class="flex-1 min-w-0 text-amber-600 dark:text-amber-400">{{ sparklineSvg | safe }}</div> <div class="flex-1 min-w-0 text-amber-600 dark:text-amber-400">{{ sparklineSvg | safe }}</div>
{% endif %} {% endif %}
</div> </div>
<p class="text-surface-600 dark:text-surface-400 mb-6 sm:mb-8"> <p class="text-surface-600 dark:text-surface-400 mb-6 sm:mb-8">
All posts including articles and notes. All posts including articles and notes.
<span class="text-sm">({{ collections.posts.length }} total)</span> <span class="text-sm">({{ collections.listedPosts.length }} total)</span>
</p> </p>
{% if paginatedPosts.length > 0 %} {% if paginatedPosts.length > 0 %}
<nav class="flex flex-wrap gap-2 mb-6" aria-label="Filter by post type"> <nav class="flex flex-wrap gap-2 mb-6" aria-label="Filter by post type">
<a href="/blog/" class="px-3 py-1.5 text-sm font-medium rounded-full bg-accent-600 text-white dark:bg-accent-700">All Posts <span class="opacity-75">({{ collections.posts.length }})</span></a> <a href="/blog/" class="px-3 py-1.5 text-sm font-medium rounded-full bg-accent-600 text-white dark:bg-accent-700">All Posts <span class="opacity-75">({{ collections.listedPosts.length }})</span></a>
{% for pt in enabledPostTypes %} {% for pt in enabledPostTypes %}
{% set collName = pt.label | lower %} {% set collName = pt.label | lower %}
<a href="{{ pt.path }}" class="px-3 py-1.5 text-sm font-medium rounded-full bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-700 border border-surface-200 dark:border-surface-700 transition-colors">{{ pt.label }}{% if collections[collName] %} <span class="text-surface-600 dark:text-surface-400">({{ collections[collName].length }})</span>{% endif %}</a> <a href="{{ pt.path }}" class="px-3 py-1.5 text-sm font-medium rounded-full bg-surface-100 dark:bg-surface-800 text-surface-600 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-700 border border-surface-200 dark:border-surface-700 transition-colors">{{ pt.label }}{% if collections[collName] %} <span class="text-surface-600 dark:text-surface-400">({{ collections[collName].length }})</span>{% endif %}</a>

View File

@@ -876,20 +876,23 @@ export default function (eleventyConfig) {
}; };
}); });
// Helper: exclude drafts from collections
const isPublished = (item) => !item.data.draft;
// Helper: exclude unlisted visibility from public listing surfaces
const isListed = (item) => {
const data = item?.data || {};
const rawVisibility = data.visibility ?? data.properties?.visibility;
const visibility = Array.isArray(rawVisibility) ? rawVisibility[0] : rawVisibility;
return String(visibility ?? "").toLowerCase() !== "unlisted";
};
// Exclude unlisted posts from UI slices like homepage/sidebar recent-post lists. // Exclude unlisted posts from UI slices like homepage/sidebar recent-post lists.
eleventyConfig.addFilter("excludeUnlistedPosts", (posts) => { eleventyConfig.addFilter("excludeUnlistedPosts", (posts) => {
if (!Array.isArray(posts)) return []; if (!Array.isArray(posts)) return [];
return posts.filter((post) => { return posts.filter(isListed);
const data = post?.data || {};
const rawVisibility = data.visibility ?? data.properties?.visibility;
const visibility = Array.isArray(rawVisibility) ? rawVisibility[0] : rawVisibility;
return String(visibility ?? "").toLowerCase() !== "unlisted";
});
}); });
// Helper: exclude drafts from collections
const isPublished = (item) => !item.data.draft;
// Collections for different post types // Collections for different post types
// Note: content path is content/ due to symlink structure // Note: content path is content/ due to symlink structure
// "posts" shows ALL content types combined // "posts" shows ALL content types combined
@@ -900,6 +903,13 @@ export default function (eleventyConfig) {
.sort((a, b) => b.date - a.date); .sort((a, b) => b.date - a.date);
}); });
eleventyConfig.addCollection("listedPosts", function (collectionApi) {
return collectionApi
.getFilteredByGlob("content/**/*.md")
.filter((item) => isPublished(item) && isListed(item))
.sort((a, b) => b.date - a.date);
});
eleventyConfig.addCollection("notes", function (collectionApi) { eleventyConfig.addCollection("notes", function (collectionApi) {
return collectionApi return collectionApi
.getFilteredByGlob("content/notes/**/*.md") .getFilteredByGlob("content/notes/**/*.md")
@@ -907,6 +917,13 @@ export default function (eleventyConfig) {
.sort((a, b) => b.date - a.date); .sort((a, b) => b.date - a.date);
}); });
eleventyConfig.addCollection("listedNotes", function (collectionApi) {
return collectionApi
.getFilteredByGlob("content/notes/**/*.md")
.filter((item) => isPublished(item) && isListed(item))
.sort((a, b) => b.date - a.date);
});
eleventyConfig.addCollection("articles", function (collectionApi) { eleventyConfig.addCollection("articles", function (collectionApi) {
return collectionApi return collectionApi
.getFilteredByGlob("content/articles/**/*.md") .getFilteredByGlob("content/articles/**/*.md")

View File

@@ -3,7 +3,7 @@ layout: layouts/base.njk
title: Notes title: Notes
withSidebar: true withSidebar: true
pagination: pagination:
data: collections.notes data: collections.listedNotes
size: 20 size: 20
alias: paginatedNotes alias: paginatedNotes
generatePageOnEmptyData: true generatePageOnEmptyData: true
@@ -12,14 +12,14 @@ permalink: "notes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
<div class="h-feed"> <div class="h-feed">
<div class="flex flex-wrap items-center gap-4 mb-2"> <div class="flex flex-wrap items-center gap-4 mb-2">
<h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100">Notes</h1> <h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100">Notes</h1>
{% set sparklineSvg = collections.notes | postingFrequency %} {% set sparklineSvg = collections.listedNotes | postingFrequency %}
{% if sparklineSvg %} {% if sparklineSvg %}
<div class="flex-1 min-w-0 text-teal-600 dark:text-teal-400">{{ sparklineSvg | safe }}</div> <div class="flex-1 min-w-0 text-teal-600 dark:text-teal-400">{{ sparklineSvg | safe }}</div>
{% endif %} {% endif %}
</div> </div>
<p class="text-surface-600 dark:text-surface-400 mb-6 sm:mb-8"> <p class="text-surface-600 dark:text-surface-400 mb-6 sm:mb-8">
Short thoughts, updates, and quick posts. Short thoughts, updates, and quick posts.
<span class="text-sm">({{ collections.notes.length }} total)</span> <span class="text-sm">({{ collections.listedNotes.length }} total)</span>
</p> </p>
{% if paginatedNotes.length > 0 %} {% if paginatedNotes.length > 0 %}

View File

@@ -3,7 +3,7 @@ layout: layouts/base.njk
title: Blog title: Blog
withSidebar: true withSidebar: true
pagination: pagination:
data: collections.posts data: collections.listedPosts
size: 20 size: 20
alias: paginatedPosts alias: paginatedPosts
permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}" permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
@@ -11,14 +11,14 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
<div class="h-feed"> <div class="h-feed">
<div class="flex flex-wrap items-center gap-4 mb-2"> <div class="flex flex-wrap items-center gap-4 mb-2">
<h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100">Blog</h1> <h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100">Blog</h1>
{% set sparklineSvg = collections.posts | postingFrequency %} {% set sparklineSvg = collections.listedPosts | postingFrequency %}
{% if sparklineSvg %} {% if sparklineSvg %}
<span class="text-amber-600 dark:text-amber-400">{{ sparklineSvg | safe }}</span> <span class="text-amber-600 dark:text-amber-400">{{ sparklineSvg | safe }}</span>
{% endif %} {% endif %}
</div> </div>
<p class="text-surface-600 dark:text-surface-400 mb-6 sm:mb-8"> <p class="text-surface-600 dark:text-surface-400 mb-6 sm:mb-8">
All posts including articles and notes. All posts including articles and notes.
<span class="text-sm">({{ collections.posts.length }} total)</span> <span class="text-sm">({{ collections.listedPosts.length }} total)</span>
</p> </p>
{% if paginatedPosts.length > 0 %} {% if paginatedPosts.length > 0 %}

View File

@@ -818,20 +818,23 @@ export default function (eleventyConfig) {
}; };
}); });
// Helper: exclude drafts from collections
const isPublished = (item) => !item.data.draft;
// Helper: exclude unlisted visibility from public listing surfaces
const isListed = (item) => {
const data = item?.data || {};
const rawVisibility = data.visibility ?? data.properties?.visibility;
const visibility = Array.isArray(rawVisibility) ? rawVisibility[0] : rawVisibility;
return String(visibility ?? "").toLowerCase() !== "unlisted";
};
// Exclude unlisted posts from UI slices like homepage/sidebar recent-post lists. // Exclude unlisted posts from UI slices like homepage/sidebar recent-post lists.
eleventyConfig.addFilter("excludeUnlistedPosts", (posts) => { eleventyConfig.addFilter("excludeUnlistedPosts", (posts) => {
if (!Array.isArray(posts)) return []; if (!Array.isArray(posts)) return [];
return posts.filter((post) => { return posts.filter(isListed);
const data = post?.data || {};
const rawVisibility = data.visibility ?? data.properties?.visibility;
const visibility = Array.isArray(rawVisibility) ? rawVisibility[0] : rawVisibility;
return String(visibility ?? "").toLowerCase() !== "unlisted";
});
}); });
// Helper: exclude drafts from collections
const isPublished = (item) => !item.data.draft;
// Collections for different post types // Collections for different post types
// Note: content path is content/ due to symlink structure // Note: content path is content/ due to symlink structure
// "posts" shows ALL content types combined // "posts" shows ALL content types combined
@@ -842,6 +845,13 @@ export default function (eleventyConfig) {
.sort((a, b) => b.date - a.date); .sort((a, b) => b.date - a.date);
}); });
eleventyConfig.addCollection("listedPosts", function (collectionApi) {
return collectionApi
.getFilteredByGlob("content/**/*.md")
.filter((item) => isPublished(item) && isListed(item))
.sort((a, b) => b.date - a.date);
});
eleventyConfig.addCollection("notes", function (collectionApi) { eleventyConfig.addCollection("notes", function (collectionApi) {
return collectionApi return collectionApi
.getFilteredByGlob("content/notes/**/*.md") .getFilteredByGlob("content/notes/**/*.md")
@@ -849,6 +859,13 @@ export default function (eleventyConfig) {
.sort((a, b) => b.date - a.date); .sort((a, b) => b.date - a.date);
}); });
eleventyConfig.addCollection("listedNotes", function (collectionApi) {
return collectionApi
.getFilteredByGlob("content/notes/**/*.md")
.filter((item) => isPublished(item) && isListed(item))
.sort((a, b) => b.date - a.date);
});
eleventyConfig.addCollection("articles", function (collectionApi) { eleventyConfig.addCollection("articles", function (collectionApi) {
return collectionApi return collectionApi
.getFilteredByGlob("content/articles/**/*.md") .getFilteredByGlob("content/articles/**/*.md")

View File

@@ -3,7 +3,7 @@ layout: layouts/base.njk
title: Notes title: Notes
withSidebar: true withSidebar: true
pagination: pagination:
data: collections.notes data: collections.listedNotes
size: 20 size: 20
alias: paginatedNotes alias: paginatedNotes
generatePageOnEmptyData: true generatePageOnEmptyData: true
@@ -12,14 +12,14 @@ permalink: "notes/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumbe
<div class="h-feed"> <div class="h-feed">
<div class="flex flex-wrap items-center gap-4 mb-2"> <div class="flex flex-wrap items-center gap-4 mb-2">
<h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100">Notes</h1> <h1 class="text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100">Notes</h1>
{% set sparklineSvg = collections.notes | postingFrequency %} {% set sparklineSvg = collections.listedNotes | postingFrequency %}
{% if sparklineSvg %} {% if sparklineSvg %}
<span class="text-amber-600 dark:text-amber-400">{{ sparklineSvg | safe }}</span> <span class="text-amber-600 dark:text-amber-400">{{ sparklineSvg | safe }}</span>
{% endif %} {% endif %}
</div> </div>
<p class="text-surface-600 dark:text-surface-400 mb-6 sm:mb-8"> <p class="text-surface-600 dark:text-surface-400 mb-6 sm:mb-8">
Short thoughts, updates, and quick posts. Short thoughts, updates, and quick posts.
<span class="text-sm">({{ collections.notes.length }} total)</span> <span class="text-sm">({{ collections.listedNotes.length }} total)</span>
</p> </p>
{% if paginatedNotes.length > 0 %} {% if paginatedNotes.length > 0 %}