Commit Graph

374 Commits

Author SHA1 Message Date
Ricardo
3bf0e78f74 feat: add filtered section templates for work/personal type distinction
Add thin-wrapper templates for work/personal filtering of CV sections:
- 8 new templates: cv-{experience,education,skills,interests}-{personal,work}.njk
- cv-languages.njk: standalone languages section (split from education)
- homepage-section.njk: 9 new routes for filtered variants
- cv.njk: uses work-only variants for the /cv/ page
- Base templates: filterType support in experience, education, skills, interests
- _data/cv.js: skillTypes and interestTypes fallback fields
2026-02-20 13:11:37 +01:00
Ricardo
334b8fdcf5 fix: revert unfurl from recent-posts section
Async shortcodes inside deeply nested Nunjucks includes
(homepage-builder → homepage-section → recent-posts) cause silent
template failures. Keep unfurl in top-level collection pages only.
2026-02-20 12:53:42 +01:00
Ricardo
0f496d624f feat: add unfurl cards to collection views and homepage
Show rich link preview cards in bookmarks, likes, replies, reposts
collection pages and the homepage recent posts section. URLs are
fetched once and cached — the same cache serves all templates.
2026-02-20 12:39:11 +01:00
Ricardo
41c7fae2f1 fix: use proper User-Agent for unfurl requests
The default facebookexternalhit UA causes many sites to block or
redirect-loop, resulting in timeouts. Switch to a well-identified
bot UA that sites handle correctly.
2026-02-20 12:31:42 +01:00
Ricardo
e0cbf8121e fix: unfurl shortcode concurrency control and failure caching
- Increase timeout from 10s to 20s
- Cache failures for 1 day (avoids retrying every build)
- Add concurrency limiter (max 5 parallel requests)
- Refactor into renderCard/renderFallbackLink helpers
2026-02-20 12:20:28 +01:00
Ricardo
9ab4ebb84a feat: add unfurl cards to reply context (likes, bookmarks, replies, reposts)
The target URL in likes, bookmarks, replies, and reposts now renders
as a rich OpenGraph card via the unfurl shortcode instead of a bare
link. The raw URL remains below the card for h-cite microformat
compatibility. Results are cached in .cache/unfurl/ for 1 week.
2026-02-20 12:01:55 +01:00
Ricardo
897daca686 feat: add unfurl shortcode for rich link preview cards
Adds {% unfurl "URL" %} shortcode that renders any URL as a rich card
with OpenGraph metadata (title, description, image, favicon). Uses
unfurl.js locally — no external API dependency. Results cached for 1
week in .cache/unfurl/. Also fixes Mastodon embed server config
(mstdn.social → indieweb.social).
2026-02-20 11:29:11 +01:00
Ricardo
656a70eb0e feat: graceful no-JS fallback and noscript handling
- Add <noscript><style> in base.njk that unhides x-cloak/x-show content,
  hides FAB and tab buttons when JS is disabled (content stacks instead)
- Add noscript message on search page with links to blog/categories
- Add noscript banner on interactions page explaining inbound tab needs JS
2026-02-19 17:35:21 +01:00
Ricardo
03ace58be5 feat: add ActivityPub badge and platform detection for interactions
- Add ActivityPub/Fediverse platform badge (purple, network icon) to
  interactions page alongside existing Mastodon and Bluesky badges
- Detect platform from Bridgy source URLs and author URLs for
  webmention.io items that lack a platform field
- Filter self-referencing syndication URLs from "Also on" footer so
  self-hosted AP posts don't show a redundant link back to the site
2026-02-19 16:52:44 +01:00
Ricardo
cb68b7be00 fix: add rel=nofollow to FAB admin links
AI crawlers (GPTBot) were following edit/create links in the static
HTML, flooding the server with /posts/edit and /session/login requests.
Adding nofollow tells well-behaved crawlers to skip these admin-only links.
2026-02-18 22:17:38 +01:00
rmdes
fc9f5968da feat: dual-fetch from conversations API for enriched interaction data
Fetch from both /webmentions/api/mentions and /conversations/api/mentions,
merge results with conversations items taking priority (richer metadata),
and display platform badges (Mastodon/Bluesky icons) on interaction cards.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:28:01 +01:00
Ricardo
216cfa21bd fix: use directories.output for pagefind indexing
The eleventy.after hook's dir.output reflects the config default (_site),
not the --output CLI flag used by start.sh. Use directories.output which
reflects the actual resolved output path.
2026-02-18 17:42:11 +01:00
Ricardo
a0040c949d fix: pagefind output directory to match template references
Templates reference /pagefind/pagefind-ui.js but pagefind defaulted
to _pagefind/ (with underscore). Added --output-subdir pagefind flag
and updated ignores to match.
2026-02-18 17:31:00 +01:00
Ricardo
2ebe63ffff fix: use eleventyComputed for OG slug to avoid Nunjucks race condition
Eleventy 3.x renders Nunjucks templates in parallel, causing page.url
to return wrong values in {% set %} tags. This caused OG images to be
mismatched between pages (e.g., bookmark showed note's OG image).

Move ogSlug and hasOgImage computation to eleventyComputed, which runs
during the sequential data cascade phase before parallel rendering.
The computed values are then available as plain template variables.

Refs: https://github.com/11ty/eleventy/issues/3183
2026-02-18 17:17:30 +01:00
Ricardo
b065dbfed7 fix: use full date-prefixed filenames for OG images
The Nunjucks race condition in Eleventy 3.x affects page.url too —
its value changes between {% set %} and {{ }} within the same
template render during parallel builds. Instead of trying to derive
slugs from page data, name OG images with the full filename
(including date prefix) to match URL path segments exactly.
2026-02-18 17:08:05 +01:00
Ricardo
8d800e2c28 fix: derive OG slug from page.url to avoid Nunjucks race condition
page.fileSlug suffers from a race condition in Eleventy 3.x parallel
rendering where Nunjucks shares state across template compilations,
causing slugs to get mixed up between pages. page.url is always
correct, so derive the OG slug from it instead.
2026-02-18 16:48:11 +01:00
Ricardo
cd8a218afb fix: exclude .cache/og from watcher to prevent build loop
OG image generation writes to .cache/og/ which the watcher detects
as file changes, triggering another build that runs OG generation
again in an infinite loop.
2026-02-18 16:10:55 +01:00
Ricardo
db1b922943 fix: generate OG images on watcher rebuilds
Remove the incremental skip guard from OG generation. The manifest
caching already handles performance — only new posts without an
existing OG image get generated (~200ms each). Without this fix,
posts created via Micropub never got OG images until container restart.
2026-02-18 15:44:10 +01:00
Ricardo
538e94f582 fix: declare collection dependencies for incremental builds
Add eleventyImport.collections to templates that access collections
via Nunjucks loops (not pagination frontmatter). Without this,
Eleventy's --incremental mode skips these pages when content changes.

- index.njk: posts, feed (homepage recent posts widget)
- feed.njk: feed (RSS must update with new posts)
- feed-json.njk: feed (JSON feed must update with new posts)
- slashes.njk: pages (slash pages index)
- categories-index.njk: categories (categories index)
2026-02-18 14:42:02 +01:00
Ricardo
ff157c838d feat: guard Eleventy hooks for incremental rebuild mode
Skip OG image generation, Pagefind indexing, and WebSub notification
during incremental rebuilds. These expensive operations only run on
full builds (container restart), not on every content change.
2026-02-18 12:23:10 +01:00
Ricardo
daf813e192 fix: wrap webmentions widget in is-land for lazy loading
The webmentions sidebar widget uses Alpine.js + fetch API but was
missing the is-land on:visible wrapper that all other JS-dependent
sidebar widgets have. This defers its initialization until scrolled
into view, consistent with social-activity, github-repos, funkwhale,
blogroll, and feedland widgets.
2026-02-18 12:20:38 +01:00
Ricardo
0d63dde15c fix: wrap tables in table-saw elements for responsive behavior
The @zachleat/table-saw component requires tables to be wrapped in
<table-saw> elements. Added an Eleventy transform to do this
automatically for all HTML output.
2026-02-18 12:12:28 +01:00
Ricardo
f7bc5d9112 feat: add posting frequency sparklines to all post type pages
Each collection page (articles, notes, photos, bookmarks, likes,
replies, reposts) now shows its own sparkline next to the heading,
showing that specific post type's frequency over the last 12 months.
2026-02-18 12:02:49 +01:00
Ricardo
113d0d55dd fix: generate sparkline SVG inline instead of using external service
The v1.sparkline.11ty.dev service returns 429 errors. Generate the
sparkline polyline SVG at build time and inline it directly.
2026-02-18 11:42:09 +01:00
Ricardo
c3eb04570c feat: add zachleat.com-inspired theme enhancements
- Add time-difference web component for relative dates
- Add @zachleat/table-saw for responsive tables
- Add webmention facepile styling with bookmarks support
- Add OG image thumbnails to post navigation
- Add @11ty/is-land for lazy widget hydration
- Wrap sidebar widgets in is-land for deferred loading
- Lazy-load webmention avatars with is-land
- Add @zachleat/filter-container for blog archive filtering
- Add posting frequency sparkline to blog header
- Inline critical CSS and defer full stylesheet loading
2026-02-18 11:16:33 +01:00
Ricardo
e5b0fd7dc6 fix: gracefully handle htmlmin parse errors instead of crashing build
Malformed HTML (e.g. unescaped quotes in iframe title attributes) caused
html-minifier-terser to throw a fatal parse error, killing the entire
Eleventy build. Now catches the error, logs a warning, and returns the
unminified content so the build completes.
2026-02-18 09:59:00 +01:00
Ricardo
8503237d4d fix(og): match Eleventy's page.fileSlug by stripping date prefix
Eleventy v3 parses YYYY-MM-DD- from filenames and removes it from
page.fileSlug. The OG generator was using the full filename (with
date prefix) causing a slug mismatch — hasOgImage filter checked
for 'slug.png' while the file was 'YYYY-MM-DD-slug.png'.

Also removes debug logging from hasOgImage filter.
2026-02-18 09:42:10 +01:00
Ricardo
719ce101b7 debug: add OG filter debug logging 2026-02-18 09:18:52 +01:00
Ricardo
de09db6171 fix: skip hidden images in extractFirstImage, fix OG subprocess NODE_OPTIONS
Two fixes:
- extractFirstImage filter now skips <img> tags with the hidden attribute,
  preventing the author avatar microformat from being used as og:image
- Clear NODE_OPTIONS in the OG subprocess env and pass --max-old-space-size
  as a direct CLI flag to avoid conflict with parent process settings
2026-02-18 08:59:25 +01:00
Ricardo
86cbc1ee5d fix: run OG image generation in subprocess to prevent OOM kill
The OG generation in the eleventy.before hook consumed too much memory
alongside Eleventy's data cascade, causing the Eleventy process to be
OOM-killed on Cloudron. Fix by running OG generation in a separate
child process with its own 768MB heap limit. Also write the manifest
incrementally (every 10 images) to preserve progress if interrupted.
2026-02-18 08:49:23 +01:00
rmdes
afc394525b feat: add textcasting support, feed attachments, and protocol badges
- Add _textcasting extension to JSON feed with support/monetization config
- Add feedAttachments filter for photo/audio/video media in feed items
- Add content_text and date_modified fields to feed items
- Add protocol badges (ATmosphere, Fediverse, Web) on reply posts
- Add support configuration via environment variables in site data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 08:42:59 +01:00
Ricardo
fe06fe3f4f feat: auto-generate OpenGraph images for posts without photos
Uses Satori + @resvg/resvg-js to create branded 1200x630 social
preview cards at build time. Cards show post title, type badge,
date, and site name on a dark background with blue accent.

Generated images are cached in .cache/og/ (persistent on Cloudron)
and passthrough-copied to the output. Posts with photos continue
using their own images. Untitled posts (notes) use body text.
2026-02-18 08:37:50 +01:00
Ricardo
4e2f579e71 feat: add heading anchors for direct linking to article sections
Uses markdown-it-anchor to generate linkable IDs on h2-h4 headings.
Headings become clickable links with a subtle # indicator on hover.
Includes scroll-margin-top so anchored headings don't hide behind
the sticky header.
2026-02-17 19:09:09 +01:00
Ricardo
ed24ac47bd feat: add 3-dot menu to feedland widget
Links to blogroll page, OPML export, and FeedLand source.
Matches Dave Winer's blogroll.js menu pattern.
2026-02-17 18:35:01 +01:00
Ricardo
69e0032891 feat: use lastItemAt for feed freshness in feedland widget
Shows when a feed last published new content instead of when it was
last checked. Falls back to lastFetchAt for blogs not yet re-synced.
2026-02-17 17:43:53 +01:00
Ricardo
9963578bab refactor: rewrite feedland widget with pure divs, no table element
Eliminates persistent horizontal scrollbar caused by <table> layout.
Uses flexbox divs exclusively for predictable overflow behavior.
2026-02-17 17:18:06 +01:00
Ricardo
5a3fb29d85 fix: eliminate horizontal scrollbar in feedland widget 2026-02-17 17:08:20 +01:00
Ricardo
8aeef55ed0 fix: ensure feed timestamp stays visible after title in feedland widget 2026-02-17 16:59:25 +01:00
Ricardo
1865ff130d fix: move feed timestamp to right of title in feedland widget 2026-02-17 16:54:00 +01:00
Ricardo
c2a6d9b280 fix: use flexbox for feed title row to keep 'when' timestamp visible 2026-02-17 16:41:38 +01:00
Ricardo
a25cc140ee fix: restore 'when' timestamp visibility in FeedLand widget 2026-02-17 16:24:31 +01:00
Ricardo
3da40ce3b0 fix: remove horizontal scrollbar from FeedLand widget 2026-02-17 16:09:36 +01:00
Ricardo
4662f2e911 fix: make FeedLand widget responsive to fill sidebar width 2026-02-17 16:03:32 +01:00
Ricardo
fec999793d fix: FeedLand widget click-to-expand and dark mode for expanded items
Add row selection and expand/collapse behavior matching Dave Winer's
blogroll.js: first click selects, second click (or caret click) expands
to show up to 5 recent items fetched from blogroll API. Items cached
after first fetch. Added dark mode styles for expanded items.
2026-02-17 15:54:31 +01:00
Ricardo
690a10ecf8 feat: add FeedLand sidebar widget
Replicates Dave Winer's blogroll.js visual rendering (240px bordered
container, Ubuntu/Rancho fonts, Title/When sort, caret wedges, truncated
titles, relative timestamps, "Powered by FeedLand" footer) using Alpine.js
and the blogroll API instead of jQuery + external scripts.

Registered in all three sidebar types (homepage, blog listing, blog post)
and in the fallback sidebar.
2026-02-17 15:26:26 +01:00
Ricardo
e2d35b541e feat: add source tabs and recent sort to blogroll widget
Sidebar widget now fetches blogs sorted by recently updated and groups
them into tabs by source type (Microsub/FeedLand). Tabs only appear
when multiple source types exist. Each tab shows up to 8 blogs.
2026-02-17 14:20:19 +01:00
Ricardo
29a014506d feat: add "Edit this post" button to FAB menu
When viewing a post that has mpUrl in its frontmatter, the FAB menu
shows an "Edit this post" link at the top that redirects to the
Indiekit admin edit form via /posts/edit?url=<mpUrl>.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 04:11:20 +01:00
Ricardo
c2ebee696d docs: update CLAUDE.md and README.md with comprehensive documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:24:14 +01:00
Ricardo
dcccf29713 feat: add personal/work project section templates
- cv-projects-personal.njk: filters projects with projectType=personal (or unset)
- cv-projects-work.njk: filters projects with projectType=work
- CV page now only shows work projects
- Homepage section dispatcher routes both new types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 11:24:34 +01:00
Ricardo
28bc7a6c1b fix: load pagefind at end of body instead of deferred in head
The defer + DOMContentLoaded queue approach failed silently when
pagefind-ui.js couldn't load. Moving the script to end of body
ensures all DOM elements exist and processes the queue immediately
after the script loads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 11:03:02 +01:00