feat: add soft-delete filter and content-warning support
Filter posts with `deleted: true` from all collections so soft-deleted posts no longer appear on the blog. Add content-warning support: on listing pages, CW posts show a warning label instead of content; on single post pages, content is wrapped in a collapsible <details>. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,9 @@ Posts declare AI involvement level in front matter (e.g. `aiCode: T1/C2`). Rende
|
||||
### Nested tags
|
||||
Categories use Obsidian-style path notation (`lang/de`, `tech/programming`). The `nestedSlugify()` function in `eleventy.config.js` preserves `/` separators during slug generation. Slugification is applied per segment.
|
||||
|
||||
### Changelog
|
||||
`changelog.njk` — public page at `/changelog/` showing development activity. Uses Alpine.js to fetch commits from the IndieKit server's GitHub endpoint (`/github/api/changelog`). Commits are categorised by commit-message prefix (`feat:` → Features, `fix:` → Fixes, `perf:` → Performance, `a11y:` → Accessibility, `docs:` → Docs, everything else → Other). The server-side categorisation is applied by the postinstall patch `patch-endpoint-github-changelog-categories.mjs` in `indiekit-blog`. Tabs, labels, and colours in `changelog.njk` must stay in sync with that patch.
|
||||
|
||||
### Unfurl shortcode
|
||||
`{% unfurl url %}` generates a rich link preview card with caching. Cache lives in `.cache/unfurl/`. The shortcode is registered from `lib/unfurl-shortcode.js`.
|
||||
|
||||
@@ -131,4 +134,5 @@ BLUESKY_HANDLE svemagie
|
||||
- **Webmention self-filter** — own Bluesky account filtered from interactions
|
||||
- **Markdown Agents** — clean Markdown served to AI crawlers
|
||||
- **Mermaid diagrams** — `eleventy-plugin-mermaid` integrated
|
||||
- **Changelog page** — commit-type tabs (feat/fix/perf/a11y/docs) via IndieKit GitHub endpoint
|
||||
- **Upstream drift check script** — `scripts/check-upstream-widget-drift.mjs`
|
||||
|
||||
@@ -80,9 +80,22 @@ withBlogSidebar: true
|
||||
|
||||
{% set isInteraction = replyTo or likedUrl or repostedUrl or bookmarkedUrl %}
|
||||
{% set hasContent = content and content | striptags | trim %}
|
||||
{% set contentWarning = contentWarning or content_warning %}
|
||||
{% if contentWarning %}
|
||||
<details class="content-warning mb-4">
|
||||
<summary class="cursor-pointer inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-200 text-sm font-medium">
|
||||
<span>⚠ {{ contentWarning }}</span>
|
||||
<span class="text-xs text-amber-600 dark:text-amber-400">(click to show)</span>
|
||||
</summary>
|
||||
<div class="e-content prose prose-surface dark:prose-invert max-w-none mt-4{% if isInteraction and hasContent %} border-l-[3px] border-l-accent-500 dark:border-l-accent-400 pl-4{% endif %}">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
</details>
|
||||
{% else %}
|
||||
<div class="e-content prose prose-surface dark:prose-invert max-w-none{% if isInteraction and hasContent %} border-l-[3px] border-l-accent-500 dark:border-l-accent-400 pl-4{% endif %}">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Rich reply context with h-cite microformat #}
|
||||
{% include "components/reply-context.njk" %}
|
||||
|
||||
31
blog.njk
31
blog.njk
@@ -37,6 +37,7 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
||||
{% set repostedUrl = post.data.repostOf or post.data.repost_of %}
|
||||
{% set replyToUrl = post.data.inReplyTo or post.data.in_reply_to %}
|
||||
{% set hasPhotos = post.data.photo and post.data.photo.length %}
|
||||
{% set postCW = post.data.contentWarning or post.data.content_warning %}
|
||||
{% set borderClass = "" %}
|
||||
{% if likedUrl %}
|
||||
{% set borderClass = "border-l-[3px] border-l-red-400 dark:border-l-red-500" %}
|
||||
@@ -86,7 +87,9 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
||||
<a class="u-like-of text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ likedUrl }}">
|
||||
{{ likedUrl }}
|
||||
</a>
|
||||
{% if post.templateContent %}
|
||||
{% if postCW %}
|
||||
<p class="mt-3 text-sm text-amber-700 dark:text-amber-300">⚠ {{ postCW }} — <a href="{{ post.url }}" class="underline">View post</a></p>
|
||||
{% elif post.templateContent %}
|
||||
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||
{{ post.templateContent | safe }}
|
||||
</div>
|
||||
@@ -131,7 +134,9 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
||||
<a class="u-bookmark-of text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ bookmarkedUrl }}">
|
||||
{{ bookmarkedUrl }}
|
||||
</a>
|
||||
{% if post.templateContent %}
|
||||
{% if postCW %}
|
||||
<p class="mt-3 text-sm text-amber-700 dark:text-amber-300">⚠ {{ postCW }} — <a href="{{ post.url }}" class="underline">View post</a></p>
|
||||
{% elif post.templateContent %}
|
||||
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||
{{ post.templateContent | safe }}
|
||||
</div>
|
||||
@@ -171,7 +176,9 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
||||
<a class="u-repost-of text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ repostedUrl }}">
|
||||
{{ repostedUrl }}
|
||||
</a>
|
||||
{% if post.templateContent %}
|
||||
{% if postCW %}
|
||||
<p class="mt-3 text-sm text-amber-700 dark:text-amber-300">⚠ {{ postCW }} — <a href="{{ post.url }}" class="underline">View post</a></p>
|
||||
{% elif post.templateContent %}
|
||||
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||
{{ post.templateContent | safe }}
|
||||
</div>
|
||||
@@ -211,9 +218,13 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
||||
<a class="u-in-reply-to text-xs text-surface-600 dark:text-surface-400 hover:underline break-all mt-1 inline-block" href="{{ replyToUrl }}">
|
||||
{{ replyToUrl }}
|
||||
</a>
|
||||
{% if postCW %}
|
||||
<p class="mt-3 text-sm text-amber-700 dark:text-amber-300">⚠ {{ postCW }} — <a href="{{ post.url }}" class="underline">View post</a></p>
|
||||
{% else %}
|
||||
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||
{{ post.templateContent | safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<a class="u-url text-sm text-sky-700 dark:text-sky-300 hover:underline mt-3 inline-block" href="{{ post.url }}" aria-label="Permalink: {{ post.data.title or ('Reply from ' + (post.date | dateDisplay)) }}">Permalink</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -246,6 +257,9 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
||||
{% endif %}
|
||||
{% include "components/garden-badge.njk" %}
|
||||
</div>
|
||||
{% if postCW %}
|
||||
<p class="mt-3 text-sm text-amber-700 dark:text-amber-300">⚠ {{ postCW }} — <a href="{{ post.url }}" class="underline">View post</a></p>
|
||||
{% else %}
|
||||
<div class="photo-gallery mt-3">
|
||||
{% for img in post.data.photo %}
|
||||
{% set photoUrl = img.url %}
|
||||
@@ -257,7 +271,8 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if post.templateContent %}
|
||||
{% endif %}
|
||||
{% if not postCW and post.templateContent %}
|
||||
<div class="e-content photo-caption prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||
{{ post.templateContent | safe }}
|
||||
</div>
|
||||
@@ -292,12 +307,16 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
||||
{% include "components/garden-badge.njk" %}
|
||||
</div>
|
||||
</div>
|
||||
{% if postCW %}
|
||||
<p class="mt-3 text-sm text-amber-700 dark:text-amber-300">⚠ {{ postCW }} — <a href="{{ post.url }}" class="underline">View post</a></p>
|
||||
{% else %}
|
||||
<p class="p-summary text-surface-700 dark:text-surface-300 mt-3">
|
||||
{{ post.templateContent | striptags | truncate(250) }}
|
||||
</p>
|
||||
<a href="{{ post.url }}" class="text-sm text-indigo-700 dark:text-indigo-300 hover:underline mt-3 inline-block">
|
||||
Read more →
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
{# ── Note card (unchanged) ── #}
|
||||
@@ -320,9 +339,13 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
||||
{% endif %}
|
||||
{% include "components/garden-badge.njk" %}
|
||||
</div>
|
||||
{% if postCW %}
|
||||
<p class="mt-3 text-sm text-amber-700 dark:text-amber-300">⚠ {{ postCW }} — <a href="{{ post.url }}" class="underline">View post</a></p>
|
||||
{% else %}
|
||||
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||
{{ post.templateContent | safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="post-footer mt-3">
|
||||
<a href="{{ post.url }}" class="text-sm text-teal-700 dark:text-teal-300 hover:underline" aria-label="Permalink: {{ post.data.title or ('Note from ' + (post.date | dateDisplay)) }}">
|
||||
Permalink
|
||||
|
||||
@@ -1028,7 +1028,7 @@ export default function (eleventyConfig) {
|
||||
});
|
||||
|
||||
// Helper: exclude drafts from collections
|
||||
const isPublished = (item) => !item.data.draft;
|
||||
const isPublished = (item) => !item.data.draft && !item.data.deleted;
|
||||
|
||||
// Helper: exclude unlisted/private visibility from public listing surfaces
|
||||
const isListed = (item) => {
|
||||
|
||||
Reference in New Issue
Block a user