Commit graph

7 commits

Author SHA1 Message Date
matt b7d473e7e5 Add RSS feed, sitemap, robots.txt, and search-engine metadata
A new "Site URL" setting (validated, trailing-slash-normalized, with
SITE_URL env fallback) anchors every absolute URL. On top of it:

- /feed.xml — RSS 2.0 with the 20 newest published posts: excerpt
  description, full sanitized HTML in content:encoded (relative image
  and link URLs rewritten to absolute, since readers resolve nothing),
  categories from tags, dc:creator, and a self atom:link. Autodiscovery
  <link> on every public page and a footer link.
- /sitemap.xml — home, post list, every published post and page
  (lastmod from updatedAt), and publicly visible tags. Rendered per
  request like the rest of the site so it never goes stale; drafts
  never appear.
- /robots.txt — allow all, disallow /admin/ and /api/, sitemap pointer.
  Admin pages also carry noindex meta as a second layer.
- Page metadata: metadataBase + canonical URLs everywhere, Open Graph
  (article type with published/modified times, author, and tags on
  posts; og:image + summary_large_image card when there's a featured
  image), and BlogPosting JSON-LD on post pages.

Gotcha encoded in lib/seo.ts: Next merges metadata shallowly, so pages
setting alternates.canonical alone would wipe the layout's RSS
autodiscovery entry — pageAlternates() always sets both.

Backups gain settings.siteUrl (export v4; older files still import).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 13:59:04 -04:00
matt 837465e81f Make Markdown typing shortcuts work everywhere in the editor
Tiptap's built-in bold/italic input rules only fire after whitespace,
so **bold** typed after punctuation (an em dash, an open paren, a
colon) never converted, and [text](url) had no input rule at all. A
small extension adds punctuation-tolerant bold/italic rules (using
lookbehinds so the preceding character isn't swallowed by the rule's
range deletion) and a link rule that fires on the closing parenthesis.
Link URLs pass the same validator as navigation links, so javascript:
URLs stay as plain text, and mid-word patterns like 2**3** are left
alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 13:05:41 -04:00
matt 35fb33c5a7 Add granular author permissions, admin-tag persistence, own-password change
Each author account now carries five grantable permissions, editable on
the Users page: publish posts, unpublish posts, delete posts (all three
scoped to the author's own posts), create tags, and approve comments
(scoped to comments on the author's posts, without delete). A bare
author writes and edits their own drafts only. Permission checks gate
the status TRANSITION, so editing an already-published post never
requires the publish permission, and the editor's status dropdown only
offers what the account may do. Tags an author creates are granted to
them automatically, and "creating" an existing off-grant tag is
refused (it would be a self-grant loophole). Existing author accounts
keep publish+unpublish via migration backfill.

Tags the admin attaches outside an author's grants now survive the
author's edits: the form shows them checked-and-locked and the server
re-attaches them on every save.

Every account can change its own password on the new /admin/account
page (current password required); the username in the admin header
links there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 12:58:19 -04:00
matt 6d84ae1224 Add author accounts with per-tag posting rights
The seeded account is the single admin; it can create author accounts
on the new /admin/users page (username + password) and grant each one
access to specific tags. Authors sign in to a Posts-only panel where
they can write, edit, publish, and unpublish their own posts — every
post must carry at least one granted tag, tags outside the grants are
rejected server-side, and only the admin can create tags or delete
posts (or anything else: pages, comments, settings, and backups stay
admin-only). Admin-only URLs bounce authors to their post list, and
foreign post editors 404.

posts.author_id records ownership; deleting an account keeps its posts
as unowned, admin-managed rows and signs the account out everywhere.
Backups (export v3) store the owner's username per post and re-attach
ownership on import when the account still exists.

Also fixes a latent form bug: a missing newTags field (author forms
don't render it) failed zod validation with an invisible error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 22:10:02 -04:00
matt bb3ab4561d Add moderated threaded comments
Visitors comment with just a name and email; a checkbox controls
whether the email is shown publicly (default private — only the admin
sees it). Every comment lands as pending and is invisible until
approved on the new /admin/comments page (approve / unapprove /
delete, with a pending-count badge in the admin nav and a dashboard
stat). Replies nest under their parent; a reply is only accepted on an
approved comment of the same post, and replies stay hidden while their
parent is unapproved so threads never render out of context. A hidden
honeypot field silently drops naive bots. Comment bodies are plain
text, rendered escaped.

The backup format gains a comments section (export version 2; v1 files
still import) with parent links remapped through file-local ids.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 21:08:08 -04:00
matt 3175bd9172 Add JSON import/export backup on the settings page
Export downloads a versioned JSON snapshot of all posts, pages, tags,
navigation, and settings from GET /api/admin/export. Cross-references
are keyed by slug rather than database id, so a backup restores cleanly
into any database. Import (a new settings-page section) validates the
file and atomically replaces all content in one transaction, sanitizing
bodies at the trust boundary; users, sessions, and uploaded files are
untouched. Server-action body limit raised so backups fit in the
import upload.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:53:38 -04:00
matt 32177b33f5 Build yap-blog platform 2026-07-02 21:32:33 -04:00