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
|
### 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.
|
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 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`.
|
`{% 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
|
- **Webmention self-filter** — own Bluesky account filtered from interactions
|
||||||
- **Markdown Agents** — clean Markdown served to AI crawlers
|
- **Markdown Agents** — clean Markdown served to AI crawlers
|
||||||
- **Mermaid diagrams** — `eleventy-plugin-mermaid` integrated
|
- **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`
|
- **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 isInteraction = replyTo or likedUrl or repostedUrl or bookmarkedUrl %}
|
||||||
{% set hasContent = content and content | striptags | trim %}
|
{% 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 %}">
|
<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 }}
|
{{ content | safe }}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# Rich reply context with h-cite microformat #}
|
{# Rich reply context with h-cite microformat #}
|
||||||
{% include "components/reply-context.njk" %}
|
{% include "components/reply-context.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 repostedUrl = post.data.repostOf or post.data.repost_of %}
|
||||||
{% set replyToUrl = post.data.inReplyTo or post.data.in_reply_to %}
|
{% set replyToUrl = post.data.inReplyTo or post.data.in_reply_to %}
|
||||||
{% set hasPhotos = post.data.photo and post.data.photo.length %}
|
{% set hasPhotos = post.data.photo and post.data.photo.length %}
|
||||||
|
{% set postCW = post.data.contentWarning or post.data.content_warning %}
|
||||||
{% set borderClass = "" %}
|
{% set borderClass = "" %}
|
||||||
{% if likedUrl %}
|
{% if likedUrl %}
|
||||||
{% set borderClass = "border-l-[3px] border-l-red-400 dark:border-l-red-500" %}
|
{% 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 }}">
|
<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 }}
|
{{ likedUrl }}
|
||||||
</a>
|
</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">
|
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||||
{{ post.templateContent | safe }}
|
{{ post.templateContent | safe }}
|
||||||
</div>
|
</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 }}">
|
<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 }}
|
{{ bookmarkedUrl }}
|
||||||
</a>
|
</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">
|
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||||
{{ post.templateContent | safe }}
|
{{ post.templateContent | safe }}
|
||||||
</div>
|
</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 }}">
|
<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 }}
|
{{ repostedUrl }}
|
||||||
</a>
|
</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">
|
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||||
{{ post.templateContent | safe }}
|
{{ post.templateContent | safe }}
|
||||||
</div>
|
</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 }}">
|
<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 }}
|
{{ replyToUrl }}
|
||||||
</a>
|
</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">
|
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||||
{{ post.templateContent | safe }}
|
{{ post.templateContent | safe }}
|
||||||
</div>
|
</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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
@@ -246,6 +257,9 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "components/garden-badge.njk" %}
|
{% 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 %}
|
||||||
<div class="photo-gallery mt-3">
|
<div class="photo-gallery mt-3">
|
||||||
{% for img in post.data.photo %}
|
{% for img in post.data.photo %}
|
||||||
{% set photoUrl = img.url %}
|
{% set photoUrl = img.url %}
|
||||||
@@ -257,7 +271,8 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
|||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</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">
|
<div class="e-content photo-caption prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||||
{{ post.templateContent | safe }}
|
{{ post.templateContent | safe }}
|
||||||
</div>
|
</div>
|
||||||
@@ -292,12 +307,16 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
|||||||
{% include "components/garden-badge.njk" %}
|
{% include "components/garden-badge.njk" %}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<p class="p-summary text-surface-700 dark:text-surface-300 mt-3">
|
||||||
{{ post.templateContent | striptags | truncate(250) }}
|
{{ post.templateContent | striptags | truncate(250) }}
|
||||||
</p>
|
</p>
|
||||||
<a href="{{ post.url }}" class="text-sm text-indigo-700 dark:text-indigo-300 hover:underline mt-3 inline-block">
|
<a href="{{ post.url }}" class="text-sm text-indigo-700 dark:text-indigo-300 hover:underline mt-3 inline-block">
|
||||||
Read more →
|
Read more →
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{# ── Note card (unchanged) ── #}
|
{# ── Note card (unchanged) ── #}
|
||||||
@@ -320,9 +339,13 @@ permalink: "blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% include "components/garden-badge.njk" %}
|
{% 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 %}
|
||||||
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
<div class="e-content prose dark:prose-invert prose-sm mt-3 max-w-none">
|
||||||
{{ post.templateContent | safe }}
|
{{ post.templateContent | safe }}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="post-footer mt-3">
|
<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)) }}">
|
<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
|
Permalink
|
||||||
|
|||||||
+1
-1
@@ -1028,7 +1028,7 @@ export default function (eleventyConfig) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Helper: exclude drafts from collections
|
// 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
|
// Helper: exclude unlisted/private visibility from public listing surfaces
|
||||||
const isListed = (item) => {
|
const isListed = (item) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user