@geekity/cms 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -4266
- package/admin/components/badge.njk +4 -0
- package/admin/components/fields.njk +41 -24
- package/admin/components/job-status.njk +45 -0
- package/admin/components/setting-fields.njk +38 -0
- package/admin/layouts/settings-page.njk +16 -0
- package/admin/pages/comments/all.njk +1 -2
- package/admin/pages/documents/editor.njk +17 -15
- package/admin/pages/documents/syndication.njk +4 -4
- package/admin/pages/federation/followers.njk +56 -6
- package/admin/pages/federation/settings.njk +1 -1
- package/admin/pages/media/library.njk +52 -3
- package/admin/pages/navigation/menus.njk +2 -2
- package/admin/pages/plugins/screen.njk +5 -46
- package/admin/pages/settings/discussion.njk +76 -47
- package/admin/pages/settings/email.njk +75 -47
- package/admin/pages/settings/general.njk +15 -15
- package/admin/pages/settings/permalinks.njk +8 -8
- package/admin/pages/settings/privacy.njk +6 -6
- package/admin/pages/settings/reading.njk +36 -36
- package/admin/pages/tools/dev-mode.njk +29 -3
- package/admin/pages/tools/export.njk +89 -0
- package/admin/pages/tools/maintenance.njk +51 -0
- package/admin/pages/users/apps.njk +2 -2
- package/admin/pages/users/edit.njk +3 -3
- package/admin/pages/users/new.njk +2 -2
- package/admin/static/admin.css +80 -0
- package/dist/access-log.d.ts +2 -10
- package/dist/access-log.d.ts.map +1 -1
- package/dist/access-log.js +1 -1
- package/dist/access-log.js.map +1 -1
- package/dist/{web → admin}/admin-bar.d.ts +3 -2
- package/dist/admin/admin-bar.d.ts.map +1 -0
- package/dist/{web → admin}/admin-bar.js +20 -17
- package/dist/admin/admin-bar.js.map +1 -0
- package/dist/{indieauth → admin}/app-activity.d.ts +2 -2
- package/dist/admin/app-activity.d.ts.map +1 -0
- package/dist/{indieauth → admin}/app-activity.js +6 -7
- package/dist/admin/app-activity.js.map +1 -0
- package/dist/admin/appearance.d.ts +4 -8
- package/dist/admin/appearance.d.ts.map +1 -1
- package/dist/admin/appearance.js +19 -20
- package/dist/admin/appearance.js.map +1 -1
- package/dist/admin/assets.d.ts +1 -8
- package/dist/admin/assets.d.ts.map +1 -1
- package/dist/admin/assets.js +2 -9
- package/dist/admin/assets.js.map +1 -1
- package/dist/admin/comments.d.ts +2 -2
- package/dist/admin/comments.d.ts.map +1 -1
- package/dist/admin/comments.js +17 -19
- package/dist/admin/comments.js.map +1 -1
- package/dist/admin/confirm-password.d.ts +4 -0
- package/dist/admin/confirm-password.d.ts.map +1 -0
- package/dist/admin/confirm-password.js +11 -0
- package/dist/admin/confirm-password.js.map +1 -0
- package/dist/{indieauth → admin}/connected-apps.d.ts +2 -2
- package/dist/admin/connected-apps.d.ts.map +1 -0
- package/dist/{indieauth → admin}/connected-apps.js +33 -39
- package/dist/admin/connected-apps.js.map +1 -0
- package/dist/admin/credentials.js +1 -1
- package/dist/admin/credentials.js.map +1 -1
- package/dist/admin/dev-mode.d.ts +1 -1
- package/dist/admin/dev-mode.d.ts.map +1 -1
- package/dist/admin/dev-mode.js +35 -10
- package/dist/admin/dev-mode.js.map +1 -1
- package/dist/admin/documents.d.ts +16 -301
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +94 -1040
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/editor-actions.d.ts +3 -2
- package/dist/admin/editor-actions.d.ts.map +1 -1
- package/dist/admin/editor-actions.js +8 -9
- package/dist/admin/editor-actions.js.map +1 -1
- package/dist/admin/editor-fields.d.ts +45 -0
- package/dist/admin/editor-fields.d.ts.map +1 -0
- package/dist/admin/editor-fields.js +71 -0
- package/dist/admin/editor-fields.js.map +1 -0
- package/dist/admin/editor-layout.d.ts +2 -10
- package/dist/admin/editor-layout.d.ts.map +1 -1
- package/dist/admin/editor-layout.js +2 -2
- package/dist/admin/editor-layout.js.map +1 -1
- package/dist/admin/export.d.ts +10 -0
- package/dist/admin/export.d.ts.map +1 -0
- package/dist/admin/export.js +72 -0
- package/dist/admin/export.js.map +1 -0
- package/dist/admin/federation.d.ts +6 -17
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +49 -104
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/flash.d.ts +2 -2
- package/dist/admin/flash.d.ts.map +1 -1
- package/dist/admin/headers.d.ts +1 -1
- package/dist/admin/headers.d.ts.map +1 -1
- package/dist/admin/headers.js +1 -1
- package/dist/admin/headers.js.map +1 -1
- package/dist/admin/index.d.ts +36 -21
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +26 -14
- package/dist/admin/index.js.map +1 -1
- package/dist/{indieauth/consent.d.ts → admin/indieauth-consent.d.ts} +4 -4
- package/dist/admin/indieauth-consent.d.ts.map +1 -0
- package/dist/{indieauth/consent.js → admin/indieauth-consent.js} +20 -23
- package/dist/admin/indieauth-consent.js.map +1 -0
- package/dist/admin/job-status.d.ts +25 -0
- package/dist/admin/job-status.d.ts.map +1 -0
- package/dist/admin/job-status.js +53 -0
- package/dist/admin/job-status.js.map +1 -0
- package/dist/admin/legacy-migrations.d.ts +131 -0
- package/dist/admin/legacy-migrations.d.ts.map +1 -0
- package/dist/admin/legacy-migrations.js +236 -0
- package/dist/admin/legacy-migrations.js.map +1 -0
- package/dist/admin/maintenance.d.ts +9 -0
- package/dist/admin/maintenance.d.ts.map +1 -0
- package/dist/admin/maintenance.js +42 -0
- package/dist/admin/maintenance.js.map +1 -0
- package/dist/admin/media.d.ts +8 -109
- package/dist/admin/media.d.ts.map +1 -1
- package/dist/admin/media.js +35 -188
- package/dist/admin/media.js.map +1 -1
- package/dist/admin/menu.d.ts.map +1 -1
- package/dist/admin/menu.js +11 -7
- package/dist/admin/menu.js.map +1 -1
- package/dist/admin/messages.d.ts +1 -1
- package/dist/admin/messages.d.ts.map +1 -1
- package/dist/admin/messages.js +11 -14
- package/dist/admin/messages.js.map +1 -1
- package/dist/admin/navigation.d.ts +3 -3
- package/dist/admin/navigation.d.ts.map +1 -1
- package/dist/admin/navigation.js +34 -33
- package/dist/admin/navigation.js.map +1 -1
- package/dist/admin/personal-data.d.ts +1 -1
- package/dist/admin/personal-data.d.ts.map +1 -1
- package/dist/admin/personal-data.js +3 -6
- package/dist/admin/personal-data.js.map +1 -1
- package/dist/admin/plugins.d.ts +1 -1
- package/dist/admin/plugins.d.ts.map +1 -1
- package/dist/admin/plugins.js +28 -79
- package/dist/admin/plugins.js.map +1 -1
- package/dist/admin/preview.d.ts +2 -1
- package/dist/admin/preview.d.ts.map +1 -1
- package/dist/admin/preview.js +31 -36
- package/dist/admin/preview.js.map +1 -1
- package/dist/admin/recovery.d.ts +3 -3
- package/dist/admin/recovery.d.ts.map +1 -1
- package/dist/admin/recovery.js +12 -14
- package/dist/admin/recovery.js.map +1 -1
- package/dist/admin/routes.d.ts +1 -3
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +34 -29
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/setting-overrides.d.ts +4 -0
- package/dist/admin/setting-overrides.d.ts.map +1 -0
- package/dist/admin/setting-overrides.js +24 -0
- package/dist/admin/setting-overrides.js.map +1 -0
- package/dist/admin/settings-discussion.d.ts +6 -4
- package/dist/admin/settings-discussion.d.ts.map +1 -1
- package/dist/admin/settings-discussion.js +79 -46
- package/dist/admin/settings-discussion.js.map +1 -1
- package/dist/admin/settings-email.d.ts +12 -9
- package/dist/admin/settings-email.d.ts.map +1 -1
- package/dist/admin/settings-email.js +94 -80
- package/dist/admin/settings-email.js.map +1 -1
- package/dist/admin/settings-federation.d.ts +0 -7
- package/dist/admin/settings-federation.d.ts.map +1 -1
- package/dist/admin/settings-federation.js +0 -36
- package/dist/admin/settings-federation.js.map +1 -1
- package/dist/admin/settings-fields.d.ts +32 -0
- package/dist/admin/settings-fields.d.ts.map +1 -0
- package/dist/admin/settings-fields.js +368 -0
- package/dist/admin/settings-fields.js.map +1 -0
- package/dist/admin/settings-general.d.ts.map +1 -1
- package/dist/admin/settings-general.js +17 -23
- package/dist/admin/settings-general.js.map +1 -1
- package/dist/admin/settings-page.d.ts +5 -32
- package/dist/admin/settings-page.d.ts.map +1 -1
- package/dist/admin/settings-page.js +39 -55
- package/dist/admin/settings-page.js.map +1 -1
- package/dist/admin/settings-pages.d.ts +1 -1
- package/dist/admin/settings-pages.d.ts.map +1 -1
- package/dist/admin/settings-permalinks.js +3 -3
- package/dist/admin/settings-permalinks.js.map +1 -1
- package/dist/admin/settings-privacy.js +1 -1
- package/dist/admin/settings-privacy.js.map +1 -1
- package/dist/admin/settings-reading.d.ts +2 -2
- package/dist/admin/settings-reading.d.ts.map +1 -1
- package/dist/admin/settings-reading.js +17 -16
- package/dist/admin/settings-reading.js.map +1 -1
- package/dist/admin/settings-renderer.d.ts +32 -0
- package/dist/admin/settings-renderer.d.ts.map +1 -0
- package/dist/admin/settings-renderer.js +79 -0
- package/dist/admin/settings-renderer.js.map +1 -0
- package/dist/admin/settings.d.ts +0 -579
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +1 -1155
- package/dist/admin/settings.js.map +1 -1
- package/dist/admin/site-setting-edit.d.ts +4 -0
- package/dist/admin/site-setting-edit.d.ts.map +1 -0
- package/dist/admin/site-setting-edit.js +20 -0
- package/dist/admin/site-setting-edit.js.map +1 -0
- package/dist/admin/syndication-targets.d.ts +1 -1
- package/dist/admin/syndication-targets.d.ts.map +1 -1
- package/dist/admin/syndication-targets.js +11 -15
- package/dist/admin/syndication-targets.js.map +1 -1
- package/dist/admin/taxonomy.d.ts +2 -2
- package/dist/admin/taxonomy.d.ts.map +1 -1
- package/dist/admin/taxonomy.js +17 -29
- package/dist/admin/taxonomy.js.map +1 -1
- package/dist/admin/templates.d.ts +2 -0
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +3 -1
- package/dist/admin/templates.js.map +1 -1
- package/dist/admin/tools.d.ts +4 -4
- package/dist/admin/tools.d.ts.map +1 -1
- package/dist/admin/tools.js +3 -6
- package/dist/admin/tools.js.map +1 -1
- package/dist/admin/uploads.d.ts +1 -106
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +2 -191
- package/dist/admin/uploads.js.map +1 -1
- package/dist/admin/users.d.ts +20 -15
- package/dist/admin/users.d.ts.map +1 -1
- package/dist/admin/users.js +84 -81
- package/dist/admin/users.js.map +1 -1
- package/dist/avatars/avatars.d.ts +2 -4
- package/dist/avatars/avatars.d.ts.map +1 -1
- package/dist/avatars/avatars.js +15 -19
- package/dist/avatars/avatars.js.map +1 -1
- package/dist/avatars/index.d.ts +1 -2
- package/dist/avatars/index.d.ts.map +1 -1
- package/dist/avatars/index.js +1 -2
- package/dist/avatars/index.js.map +1 -1
- package/dist/cache.d.ts +4 -4
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +13 -9
- package/dist/cache.js.map +1 -1
- package/dist/cli.d.ts +2 -33
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +729 -344
- package/dist/cli.js.map +1 -1
- package/dist/cms/app.d.ts +11 -0
- package/dist/cms/app.d.ts.map +1 -0
- package/dist/cms/app.js +126 -0
- package/dist/cms/app.js.map +1 -0
- package/dist/cms/boot.d.ts +26 -0
- package/dist/cms/boot.d.ts.map +1 -0
- package/dist/cms/boot.js +128 -0
- package/dist/cms/boot.js.map +1 -0
- package/dist/cms/create.d.ts +272 -0
- package/dist/cms/create.d.ts.map +1 -0
- package/dist/cms/create.js +181 -0
- package/dist/cms/create.js.map +1 -0
- package/dist/cms/lifecycle.d.ts +11 -0
- package/dist/cms/lifecycle.d.ts.map +1 -0
- package/dist/cms/lifecycle.js +15 -0
- package/dist/cms/lifecycle.js.map +1 -0
- package/dist/{web/errors.d.ts → cms/server-error.d.ts} +2 -8
- package/dist/cms/server-error.d.ts.map +1 -0
- package/dist/{web/errors.js → cms/server-error.js} +4 -14
- package/dist/cms/server-error.js.map +1 -0
- package/dist/cms/services.d.ts +57 -0
- package/dist/cms/services.d.ts.map +1 -0
- package/dist/cms/services.js +410 -0
- package/dist/cms/services.js.map +1 -0
- package/dist/cms/site-records.d.ts +4 -0
- package/dist/cms/site-records.d.ts.map +1 -0
- package/dist/cms/site-records.js +13 -0
- package/dist/cms/site-records.js.map +1 -0
- package/dist/comments/akismet.d.ts +14 -47
- package/dist/comments/akismet.d.ts.map +1 -1
- package/dist/comments/akismet.js +30 -71
- package/dist/comments/akismet.js.map +1 -1
- package/dist/comments/conversation.d.ts +340 -0
- package/dist/comments/conversation.d.ts.map +1 -0
- package/dist/comments/conversation.js +51 -0
- package/dist/comments/conversation.js.map +1 -0
- package/dist/comments/form.d.ts +2 -2
- package/dist/comments/form.d.ts.map +1 -1
- package/dist/comments/form.js +2 -2
- package/dist/comments/form.js.map +1 -1
- package/dist/comments/index.d.ts +15 -9
- package/dist/comments/index.d.ts.map +1 -1
- package/dist/comments/index.js +9 -6
- package/dist/comments/index.js.map +1 -1
- package/dist/comments/intake.d.ts +147 -0
- package/dist/comments/intake.d.ts.map +1 -0
- package/dist/comments/intake.js +161 -0
- package/dist/comments/intake.js.map +1 -0
- package/dist/comments/markdown.d.ts.map +1 -1
- package/dist/comments/markdown.js +2 -9
- package/dist/comments/markdown.js.map +1 -1
- package/dist/comments/migrations.d.ts +45 -0
- package/dist/comments/migrations.d.ts.map +1 -0
- package/dist/comments/migrations.js +194 -0
- package/dist/comments/migrations.js.map +1 -0
- package/dist/comments/moderate.d.ts +2 -2
- package/dist/comments/moderate.d.ts.map +1 -1
- package/dist/comments/moderate.js +3 -13
- package/dist/comments/moderate.js.map +1 -1
- package/dist/comments/page-form.d.ts +52 -0
- package/dist/comments/page-form.d.ts.map +1 -0
- package/dist/comments/page-form.js +62 -0
- package/dist/comments/page-form.js.map +1 -0
- package/dist/comments/policy.d.ts +24 -35
- package/dist/comments/policy.d.ts.map +1 -1
- package/dist/comments/policy.js +16 -38
- package/dist/comments/policy.js.map +1 -1
- package/dist/comments/records.d.ts +17 -195
- package/dist/comments/records.d.ts.map +1 -1
- package/dist/comments/records.js +24 -394
- package/dist/comments/records.js.map +1 -1
- package/dist/comments/reply-notices.d.ts +10 -11
- package/dist/comments/reply-notices.d.ts.map +1 -1
- package/dist/comments/reply-notices.js +32 -20
- package/dist/comments/reply-notices.js.map +1 -1
- package/dist/comments/reply-post.d.ts +2 -2
- package/dist/comments/reply-post.d.ts.map +1 -1
- package/dist/comments/reply-post.js +8 -7
- package/dist/comments/reply-post.js.map +1 -1
- package/dist/comments/routes.d.ts +3 -3
- package/dist/comments/routes.d.ts.map +1 -1
- package/dist/comments/routes.js +19 -31
- package/dist/comments/routes.js.map +1 -1
- package/dist/comments/submission.d.ts +5 -102
- package/dist/comments/submission.d.ts.map +1 -1
- package/dist/comments/submission.js +5 -22
- package/dist/comments/submission.js.map +1 -1
- package/dist/comments/viewer.d.ts +1 -25
- package/dist/comments/viewer.d.ts.map +1 -1
- package/dist/comments/viewer.js +1 -44
- package/dist/comments/viewer.js.map +1 -1
- package/dist/config-loader.d.ts +35 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +117 -0
- package/dist/config-loader.js.map +1 -0
- package/dist/config.d.ts +99 -51
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +137 -181
- package/dist/config.js.map +1 -1
- package/dist/contact/delivery.d.ts +3 -5
- package/dist/contact/delivery.d.ts.map +1 -1
- package/dist/contact/delivery.js +5 -14
- package/dist/contact/delivery.js.map +1 -1
- package/dist/contact/form.d.ts +2 -4
- package/dist/contact/form.d.ts.map +1 -1
- package/dist/contact/form.js +3 -4
- package/dist/contact/form.js.map +1 -1
- package/dist/contact/index.d.ts +3 -3
- package/dist/contact/index.d.ts.map +1 -1
- package/dist/contact/index.js +3 -3
- package/dist/contact/index.js.map +1 -1
- package/dist/contact/records.d.ts +0 -2
- package/dist/contact/records.d.ts.map +1 -1
- package/dist/contact/records.js +11 -16
- package/dist/contact/records.js.map +1 -1
- package/dist/contact/submission.d.ts +2 -2
- package/dist/contact/submission.d.ts.map +1 -1
- package/dist/contact/submission.js +7 -16
- package/dist/contact/submission.js.map +1 -1
- package/dist/{images → content}/alt-text.d.ts +1 -1
- package/dist/content/alt-text.d.ts.map +1 -0
- package/dist/{images → content}/alt-text.js +3 -5
- package/dist/content/alt-text.js.map +1 -0
- package/dist/content/authors.d.ts +79 -0
- package/dist/content/authors.d.ts.map +1 -0
- package/dist/content/authors.js +106 -0
- package/dist/content/authors.js.map +1 -0
- package/dist/content/citation.d.ts +4 -0
- package/dist/content/citation.d.ts.map +1 -1
- package/dist/content/citation.js +3 -0
- package/dist/content/citation.js.map +1 -1
- package/dist/content/comments.d.ts +3 -0
- package/dist/content/comments.d.ts.map +1 -0
- package/dist/content/comments.js +3 -0
- package/dist/content/comments.js.map +1 -0
- package/dist/content/contact.d.ts +3 -0
- package/dist/content/contact.d.ts.map +1 -0
- package/dist/content/contact.js +3 -0
- package/dist/content/contact.js.map +1 -0
- package/dist/content/document-form.d.ts +197 -0
- package/dist/content/document-form.d.ts.map +1 -0
- package/dist/content/document-form.js +201 -0
- package/dist/content/document-form.js.map +1 -0
- package/dist/content/document-kind.d.ts +55 -0
- package/dist/content/document-kind.d.ts.map +1 -0
- package/dist/content/document-kind.js +30 -0
- package/dist/content/document-kind.js.map +1 -0
- package/dist/{admin/enclosure-field.d.ts → content/enclosure-form.d.ts} +14 -34
- package/dist/content/enclosure-form.d.ts.map +1 -0
- package/dist/{admin/enclosure-field.js → content/enclosure-form.js} +43 -81
- package/dist/content/enclosure-form.js.map +1 -0
- package/dist/content/enclosure.d.ts.map +1 -1
- package/dist/content/enclosure.js +3 -4
- package/dist/content/enclosure.js.map +1 -1
- package/dist/{admin/event-field.d.ts → content/event-form.d.ts} +6 -5
- package/dist/content/event-form.d.ts.map +1 -0
- package/dist/{admin/event-field.js → content/event-form.js} +45 -12
- package/dist/content/event-form.js.map +1 -0
- package/dist/content/excerpt.d.ts +26 -0
- package/dist/content/excerpt.d.ts.map +1 -0
- package/dist/content/excerpt.js +46 -0
- package/dist/content/excerpt.js.map +1 -0
- package/dist/content/form-error.d.ts +10 -0
- package/dist/content/form-error.d.ts.map +1 -0
- package/dist/content/form-error.js +2 -0
- package/dist/content/form-error.js.map +1 -0
- package/dist/{web → content}/guids.d.ts +12 -5
- package/dist/content/guids.d.ts.map +1 -0
- package/dist/{web → content}/guids.js +28 -5
- package/dist/content/guids.js.map +1 -0
- package/dist/content/image-tags.d.ts +25 -0
- package/dist/content/image-tags.d.ts.map +1 -0
- package/dist/content/image-tags.js +55 -0
- package/dist/content/image-tags.js.map +1 -0
- package/dist/content/index.d.ts +11 -6
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +6 -3
- package/dist/content/index.js.map +1 -1
- package/dist/content/kept-properties.d.ts +0 -1
- package/dist/content/kept-properties.d.ts.map +1 -1
- package/dist/content/kept-properties.js +2 -2
- package/dist/content/kept-properties.js.map +1 -1
- package/dist/content/language.d.ts +19 -0
- package/dist/content/language.d.ts.map +1 -0
- package/dist/content/language.js +19 -0
- package/dist/content/language.js.map +1 -0
- package/dist/content/license.d.ts.map +1 -0
- package/dist/content/license.js.map +1 -0
- package/dist/{web → content}/locale.d.ts +0 -6
- package/dist/content/locale.d.ts.map +1 -0
- package/dist/{web → content}/locale.js +1 -6
- package/dist/content/locale.js.map +1 -0
- package/dist/content/location-form.d.ts +17 -0
- package/dist/content/location-form.d.ts.map +1 -0
- package/dist/{admin/location-field.js → content/location-form.js} +2 -23
- package/dist/content/location-form.js.map +1 -0
- package/dist/content/location.d.ts.map +1 -1
- package/dist/content/location.js +1 -3
- package/dist/content/location.js.map +1 -1
- package/dist/content/locations.d.ts +0 -1
- package/dist/content/locations.d.ts.map +1 -1
- package/dist/content/locations.js +2 -2
- package/dist/content/locations.js.map +1 -1
- package/dist/content/markdown-links.d.ts +11 -0
- package/dist/content/markdown-links.d.ts.map +1 -0
- package/dist/content/markdown-links.js +223 -0
- package/dist/content/markdown-links.js.map +1 -0
- package/dist/content/markdown.d.ts +2 -1
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +1 -1
- package/dist/content/markdown.js.map +1 -1
- package/dist/content/media.d.ts +7 -0
- package/dist/content/media.d.ts.map +1 -1
- package/dist/content/media.js +12 -1
- package/dist/content/media.js.map +1 -1
- package/dist/content/metadata/strip-job.d.ts +16 -0
- package/dist/content/metadata/strip-job.d.ts.map +1 -0
- package/dist/content/metadata/strip-job.js +24 -0
- package/dist/content/metadata/strip-job.js.map +1 -0
- package/dist/content/metadata/sweep.d.ts +13 -1
- package/dist/content/metadata/sweep.d.ts.map +1 -1
- package/dist/content/metadata/sweep.js +47 -25
- package/dist/content/metadata/sweep.js.map +1 -1
- package/dist/content/migrated.js +1 -1
- package/dist/content/migrated.js.map +1 -1
- package/dist/content/parser.d.ts.map +1 -1
- package/dist/content/parser.js +1 -12
- package/dist/content/parser.js.map +1 -1
- package/dist/content/permalink-file.d.ts +2 -1
- package/dist/content/permalink-file.d.ts.map +1 -1
- package/dist/content/permalink-file.js +3 -3
- package/dist/content/permalink-file.js.map +1 -1
- package/dist/content/permalinks.d.ts +34 -0
- package/dist/content/permalinks.d.ts.map +1 -0
- package/dist/{web/documents.js → content/permalinks.js} +6 -56
- package/dist/content/permalinks.js.map +1 -0
- package/dist/content/photo-form.d.ts +29 -0
- package/dist/content/photo-form.d.ts.map +1 -0
- package/dist/{admin/photo-field.js → content/photo-form.js} +43 -40
- package/dist/content/photo-form.js.map +1 -0
- package/dist/content/photo.d.ts +2 -1
- package/dist/content/photo.d.ts.map +1 -1
- package/dist/content/photo.js +2 -2
- package/dist/content/photo.js.map +1 -1
- package/dist/content/post-fields.d.ts +40 -0
- package/dist/content/post-fields.d.ts.map +1 -0
- package/dist/content/post-fields.js +252 -0
- package/dist/content/post-fields.js.map +1 -0
- package/dist/content/read-form.d.ts +31 -0
- package/dist/content/read-form.d.ts.map +1 -0
- package/dist/content/read-form.js +157 -0
- package/dist/content/read-form.js.map +1 -0
- package/dist/content/read.d.ts +9 -2
- package/dist/content/read.d.ts.map +1 -1
- package/dist/content/read.js +28 -13
- package/dist/content/read.js.map +1 -1
- package/dist/content/redirects.d.ts +84 -0
- package/dist/content/redirects.d.ts.map +1 -0
- package/dist/content/redirects.js +234 -0
- package/dist/content/redirects.js.map +1 -0
- package/dist/content/sanitize.d.ts +13 -0
- package/dist/content/sanitize.d.ts.map +1 -0
- package/dist/{web → content}/sanitize.js +4 -71
- package/dist/content/sanitize.js.map +1 -0
- package/dist/content/save.d.ts +5 -8
- package/dist/content/save.d.ts.map +1 -1
- package/dist/content/save.js +5 -26
- package/dist/content/save.js.map +1 -1
- package/dist/content/schedule.d.ts +2 -2
- package/dist/content/schedule.d.ts.map +1 -1
- package/dist/content/schedule.js +1 -3
- package/dist/content/schedule.js.map +1 -1
- package/dist/content/served.d.ts +58 -0
- package/dist/content/served.d.ts.map +1 -0
- package/dist/content/served.js +65 -0
- package/dist/content/served.js.map +1 -0
- package/dist/content/site-links.d.ts +72 -0
- package/dist/content/site-links.d.ts.map +1 -0
- package/dist/content/site-links.js +191 -0
- package/dist/content/site-links.js.map +1 -0
- package/dist/content/sqlite/archive.d.ts +4 -0
- package/dist/content/sqlite/archive.d.ts.map +1 -0
- package/dist/content/sqlite/archive.js +96 -0
- package/dist/content/sqlite/archive.js.map +1 -0
- package/dist/content/sqlite/backlinks.d.ts +4 -0
- package/dist/content/sqlite/backlinks.d.ts.map +1 -0
- package/dist/content/sqlite/backlinks.js +46 -0
- package/dist/content/sqlite/backlinks.js.map +1 -0
- package/dist/content/sqlite/catalog.d.ts +4 -0
- package/dist/content/sqlite/catalog.d.ts.map +1 -0
- package/dist/content/sqlite/catalog.js +62 -0
- package/dist/content/sqlite/catalog.js.map +1 -0
- package/dist/content/sqlite/clauses.d.ts +57 -0
- package/dist/content/sqlite/clauses.d.ts.map +1 -0
- package/dist/content/sqlite/clauses.js +76 -0
- package/dist/content/sqlite/clauses.js.map +1 -0
- package/dist/content/sqlite/database.d.ts +26 -0
- package/dist/content/sqlite/database.d.ts.map +1 -0
- package/dist/content/sqlite/database.js +27 -0
- package/dist/content/sqlite/database.js.map +1 -0
- package/dist/content/sqlite/documents.d.ts +12 -0
- package/dist/content/sqlite/documents.d.ts.map +1 -0
- package/dist/content/sqlite/documents.js +40 -0
- package/dist/content/sqlite/documents.js.map +1 -0
- package/dist/content/sqlite/lookups.d.ts +6 -0
- package/dist/content/sqlite/lookups.d.ts.map +1 -0
- package/dist/content/sqlite/lookups.js +25 -0
- package/dist/content/sqlite/lookups.js.map +1 -0
- package/dist/content/sqlite/open.d.ts +8 -0
- package/dist/content/sqlite/open.d.ts.map +1 -0
- package/dist/content/sqlite/open.js +63 -0
- package/dist/content/sqlite/open.js.map +1 -0
- package/dist/content/sqlite/replies.d.ts +4 -0
- package/dist/content/sqlite/replies.d.ts.map +1 -0
- package/dist/content/sqlite/replies.js +21 -0
- package/dist/content/sqlite/replies.js.map +1 -0
- package/dist/content/sqlite/rows.d.ts +22 -0
- package/dist/content/sqlite/rows.d.ts.map +1 -0
- package/dist/content/sqlite/rows.js +93 -0
- package/dist/content/sqlite/rows.js.map +1 -0
- package/dist/content/sqlite/schedule.d.ts +4 -0
- package/dist/content/sqlite/schedule.d.ts.map +1 -0
- package/dist/content/sqlite/schedule.js +16 -0
- package/dist/content/sqlite/schedule.js.map +1 -0
- package/dist/content/sqlite/schema.d.ts +10 -0
- package/dist/content/sqlite/schema.d.ts.map +1 -0
- package/dist/content/sqlite/schema.js +209 -0
- package/dist/content/sqlite/schema.js.map +1 -0
- package/dist/content/sqlite/search.d.ts +4 -0
- package/dist/content/sqlite/search.d.ts.map +1 -0
- package/dist/content/sqlite/search.js +54 -0
- package/dist/content/sqlite/search.js.map +1 -0
- package/dist/content/sqlite/spellings.d.ts +6 -0
- package/dist/content/sqlite/spellings.d.ts.map +1 -0
- package/dist/content/sqlite/spellings.js +80 -0
- package/dist/content/sqlite/spellings.js.map +1 -0
- package/dist/content/sqlite/taxonomy.d.ts +4 -0
- package/dist/content/sqlite/taxonomy.d.ts.map +1 -0
- package/dist/content/sqlite/taxonomy.js +75 -0
- package/dist/content/sqlite/taxonomy.js.map +1 -0
- package/dist/content/sqlite/writes.d.ts +9 -0
- package/dist/content/sqlite/writes.d.ts.map +1 -0
- package/dist/content/sqlite/writes.js +164 -0
- package/dist/content/sqlite/writes.js.map +1 -0
- package/dist/content/store.d.ts +142 -138
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +0 -1051
- package/dist/content/store.js.map +1 -1
- package/dist/content/sync.d.ts +12 -4
- package/dist/content/sync.d.ts.map +1 -1
- package/dist/content/sync.js +24 -41
- package/dist/content/sync.js.map +1 -1
- package/dist/content/syndicate-to.d.ts +25 -0
- package/dist/content/syndicate-to.d.ts.map +1 -0
- package/dist/content/syndicate-to.js +8 -0
- package/dist/content/syndicate-to.js.map +1 -0
- package/dist/{web → content}/taxonomy.d.ts +1 -1
- package/dist/content/taxonomy.d.ts.map +1 -0
- package/dist/{web → content}/taxonomy.js +2 -2
- package/dist/content/taxonomy.js.map +1 -0
- package/dist/content/time.d.ts +2 -2
- package/dist/content/time.d.ts.map +1 -1
- package/dist/content/time.js +3 -3
- package/dist/content/time.js.map +1 -1
- package/dist/content/trash.d.ts +35 -0
- package/dist/content/trash.d.ts.map +1 -0
- package/dist/content/trash.js +83 -0
- package/dist/content/trash.js.map +1 -0
- package/dist/content/uploads.d.ts +24 -0
- package/dist/content/uploads.d.ts.map +1 -0
- package/dist/content/uploads.js +32 -0
- package/dist/content/uploads.js.map +1 -0
- package/dist/content/video.d.ts.map +1 -1
- package/dist/content/video.js +3 -5
- package/dist/content/video.js.map +1 -1
- package/dist/content/write.d.ts +84 -0
- package/dist/content/write.d.ts.map +1 -0
- package/dist/content/write.js +570 -0
- package/dist/content/write.js.map +1 -0
- package/dist/content/writer.d.ts.map +1 -1
- package/dist/content/writer.js +1 -2
- package/dist/content/writer.js.map +1 -1
- package/dist/dev-mode.d.ts +6 -26
- package/dist/dev-mode.d.ts.map +1 -1
- package/dist/dev-mode.js +5 -54
- package/dist/dev-mode.js.map +1 -1
- package/dist/drain.d.ts +2 -3
- package/dist/drain.d.ts.map +1 -1
- package/dist/drain.js +2 -1
- package/dist/drain.js.map +1 -1
- package/dist/export/export-archives.d.ts +29 -0
- package/dist/export/export-archives.d.ts.map +1 -0
- package/dist/export/export-archives.js +88 -0
- package/dist/export/export-archives.js.map +1 -0
- package/dist/export/export-job.d.ts +21 -0
- package/dist/export/export-job.d.ts.map +1 -0
- package/dist/export/export-job.js +63 -0
- package/dist/export/export-job.js.map +1 -0
- package/dist/export/site-export.d.ts +101 -0
- package/dist/export/site-export.d.ts.map +1 -0
- package/dist/export/site-export.js +243 -0
- package/dist/export/site-export.js.map +1 -0
- package/dist/export/site-import.d.ts +75 -0
- package/dist/export/site-import.d.ts.map +1 -0
- package/dist/export/site-import.js +492 -0
- package/dist/export/site-import.js.map +1 -0
- package/dist/exports.d.ts +31 -0
- package/dist/exports.d.ts.map +1 -0
- package/dist/exports.js +10 -0
- package/dist/exports.js.map +1 -0
- package/dist/federation/actor.d.ts +1 -1
- package/dist/federation/actor.d.ts.map +1 -1
- package/dist/federation/actor.js +6 -13
- package/dist/federation/actor.js.map +1 -1
- package/dist/federation/article.d.ts +4 -14
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +40 -51
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/backfill.d.ts +7 -5
- package/dist/federation/backfill.d.ts.map +1 -1
- package/dist/federation/backfill.js +12 -13
- package/dist/federation/backfill.js.map +1 -1
- package/dist/federation/citations.d.ts.map +1 -1
- package/dist/federation/citations.js.map +1 -1
- package/dist/federation/cited-post.d.ts +1 -1
- package/dist/federation/cited-post.d.ts.map +1 -1
- package/dist/federation/cited-post.js +3 -5
- package/dist/federation/cited-post.js.map +1 -1
- package/dist/federation/delivery-plan.d.ts +85 -0
- package/dist/federation/delivery-plan.d.ts.map +1 -0
- package/dist/federation/delivery-plan.js +137 -0
- package/dist/federation/delivery-plan.js.map +1 -0
- package/dist/federation/delivery.d.ts +12 -34
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +79 -212
- package/dist/federation/delivery.js.map +1 -1
- package/dist/federation/federation.d.ts +12 -12
- package/dist/federation/federation.d.ts.map +1 -1
- package/dist/federation/federation.js +12 -18
- package/dist/federation/federation.js.map +1 -1
- package/dist/federation/followers.d.ts +1 -1
- package/dist/federation/followers.d.ts.map +1 -1
- package/dist/federation/handles.d.ts +1 -1
- package/dist/federation/handles.d.ts.map +1 -1
- package/dist/federation/handles.js +1 -1
- package/dist/federation/handles.js.map +1 -1
- package/dist/federation/inbox.d.ts +1 -1
- package/dist/federation/inbox.d.ts.map +1 -1
- package/dist/federation/inbox.js +6 -5
- package/dist/federation/inbox.js.map +1 -1
- package/dist/federation/index.d.ts +10 -8
- package/dist/federation/index.d.ts.map +1 -1
- package/dist/federation/index.js +7 -5
- package/dist/federation/index.js.map +1 -1
- package/dist/federation/keys.d.ts +8 -27
- package/dist/federation/keys.d.ts.map +1 -1
- package/dist/federation/keys.js +7 -50
- package/dist/federation/keys.js.map +1 -1
- package/dist/federation/mount.d.ts +2 -2
- package/dist/federation/mount.d.ts.map +1 -1
- package/dist/federation/mount.js +9 -7
- package/dist/federation/mount.js.map +1 -1
- package/dist/federation/paths.d.ts +1 -9
- package/dist/federation/paths.d.ts.map +1 -1
- package/dist/federation/paths.js +2 -12
- package/dist/federation/paths.js.map +1 -1
- package/dist/federation/profiles.d.ts +2 -2
- package/dist/federation/profiles.d.ts.map +1 -1
- package/dist/federation/profiles.js +6 -9
- package/dist/federation/profiles.js.map +1 -1
- package/dist/federation/queue.d.ts +32 -17
- package/dist/federation/queue.d.ts.map +1 -1
- package/dist/federation/queue.js +173 -39
- package/dist/federation/queue.js.map +1 -1
- package/dist/federation/quotes.d.ts +2 -2
- package/dist/federation/quotes.d.ts.map +1 -1
- package/dist/federation/quotes.js +4 -4
- package/dist/federation/quotes.js.map +1 -1
- package/dist/federation/records.d.ts +2 -2
- package/dist/federation/records.d.ts.map +1 -1
- package/dist/federation/records.js +12 -19
- package/dist/federation/records.js.map +1 -1
- package/dist/federation/relays.d.ts +8 -5
- package/dist/federation/relays.d.ts.map +1 -1
- package/dist/federation/relays.js +52 -53
- package/dist/federation/relays.js.map +1 -1
- package/dist/federation/resend-job.d.ts +44 -0
- package/dist/federation/resend-job.d.ts.map +1 -0
- package/dist/federation/resend-job.js +59 -0
- package/dist/federation/resend-job.js.map +1 -0
- package/dist/federation/site-loaders.js +1 -1
- package/dist/federation/site-loaders.js.map +1 -1
- package/dist/federation/webfinger.d.ts +23 -0
- package/dist/federation/webfinger.d.ts.map +1 -0
- package/dist/federation/webfinger.js +70 -0
- package/dist/federation/webfinger.js.map +1 -0
- package/dist/files/atomic.d.ts +9 -0
- package/dist/files/atomic.d.ts.map +1 -1
- package/dist/files/atomic.js +5 -10
- package/dist/files/atomic.js.map +1 -1
- package/dist/files/data-files.d.ts +104 -0
- package/dist/files/data-files.d.ts.map +1 -0
- package/dist/files/data-files.js +330 -0
- package/dist/files/data-files.js.map +1 -0
- package/dist/files/export-archives.d.ts +12 -0
- package/dist/files/export-archives.d.ts.map +1 -0
- package/dist/files/export-archives.js +67 -0
- package/dist/files/export-archives.js.map +1 -0
- package/dist/files/static-files.d.ts +21 -0
- package/dist/files/static-files.d.ts.map +1 -0
- package/dist/files/static-files.js +73 -0
- package/dist/files/static-files.js.map +1 -0
- package/dist/files/tar.d.ts +29 -0
- package/dist/files/tar.d.ts.map +1 -0
- package/dist/files/tar.js +206 -0
- package/dist/files/tar.js.map +1 -0
- package/dist/files/upload-types.d.ts +81 -0
- package/dist/files/upload-types.d.ts.map +1 -0
- package/dist/files/upload-types.js +234 -0
- package/dist/files/upload-types.js.map +1 -0
- package/dist/forms/checker.d.ts +93 -0
- package/dist/forms/checker.d.ts.map +1 -0
- package/dist/forms/checker.js +2 -0
- package/dist/forms/checker.js.map +1 -0
- package/dist/forms/protection.d.ts +3 -9
- package/dist/forms/protection.d.ts.map +1 -1
- package/dist/forms/protection.js +5 -11
- package/dist/forms/protection.js.map +1 -1
- package/dist/images/icons.d.ts.map +1 -1
- package/dist/images/icons.js +4 -2
- package/dist/images/icons.js.map +1 -1
- package/dist/images/index.d.ts +2 -1
- package/dist/images/index.d.ts.map +1 -1
- package/dist/images/index.js +2 -1
- package/dist/images/index.js.map +1 -1
- package/dist/images/markup.d.ts +4 -21
- package/dist/images/markup.d.ts.map +1 -1
- package/dist/images/markup.js +15 -70
- package/dist/images/markup.js.map +1 -1
- package/dist/images/paths.d.ts +1 -14
- package/dist/images/paths.d.ts.map +1 -1
- package/dist/images/paths.js +9 -23
- package/dist/images/paths.js.map +1 -1
- package/dist/images/variants.d.ts +5 -3
- package/dist/images/variants.d.ts.map +1 -1
- package/dist/images/variants.js +23 -22
- package/dist/images/variants.js.map +1 -1
- package/dist/index.d.ts +4 -291
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -839
- package/dist/index.js.map +1 -1
- package/dist/indexnow.d.ts +4 -2
- package/dist/indexnow.d.ts.map +1 -1
- package/dist/indexnow.js +52 -45
- package/dist/indexnow.js.map +1 -1
- package/dist/indieauth/client.d.ts +1 -1
- package/dist/indieauth/client.d.ts.map +1 -1
- package/dist/indieauth/client.js +1 -1
- package/dist/indieauth/client.js.map +1 -1
- package/dist/indieauth/grants.d.ts +1 -1
- package/dist/indieauth/grants.d.ts.map +1 -1
- package/dist/indieauth/identity.d.ts +2 -2
- package/dist/indieauth/identity.d.ts.map +1 -1
- package/dist/indieauth/identity.js +5 -7
- package/dist/indieauth/identity.js.map +1 -1
- package/dist/indieauth/redeem.d.ts +1 -1
- package/dist/indieauth/redeem.d.ts.map +1 -1
- package/dist/indieauth/redeem.js.map +1 -1
- package/dist/indieauth/request.d.ts +5 -1
- package/dist/indieauth/request.d.ts.map +1 -1
- package/dist/indieauth/request.js +6 -2
- package/dist/indieauth/request.js.map +1 -1
- package/dist/indieauth/tokens.d.ts +1 -3
- package/dist/indieauth/tokens.d.ts.map +1 -1
- package/dist/indieauth/tokens.js +4 -6
- package/dist/indieauth/tokens.js.map +1 -1
- package/dist/jobs/file-records.d.ts +13 -0
- package/dist/jobs/file-records.d.ts.map +1 -0
- package/dist/jobs/file-records.js +104 -0
- package/dist/jobs/file-records.js.map +1 -0
- package/dist/jobs/in-process.d.ts +10 -0
- package/dist/jobs/in-process.d.ts.map +1 -0
- package/dist/jobs/in-process.js +45 -0
- package/dist/jobs/in-process.js.map +1 -0
- package/dist/jobs/lock-file.d.ts +13 -0
- package/dist/jobs/lock-file.d.ts.map +1 -0
- package/dist/jobs/lock-file.js +107 -0
- package/dist/jobs/lock-file.js.map +1 -0
- package/dist/jobs/processes.d.ts +11 -0
- package/dist/jobs/processes.d.ts.map +1 -0
- package/dist/jobs/processes.js +50 -0
- package/dist/jobs/processes.js.map +1 -0
- package/dist/jobs/queue.d.ts +18 -0
- package/dist/jobs/queue.d.ts.map +1 -0
- package/dist/jobs/queue.js +220 -0
- package/dist/jobs/queue.js.map +1 -0
- package/dist/jobs/types.d.ts +159 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/types.js +22 -0
- package/dist/jobs/types.js.map +1 -0
- package/dist/mail/index.d.ts +1 -7
- package/dist/mail/index.d.ts.map +1 -1
- package/dist/mail/index.js +0 -3
- package/dist/mail/index.js.map +1 -1
- package/dist/mail/provider.d.ts +7 -0
- package/dist/mail/provider.d.ts.map +1 -1
- package/dist/maintenance.d.ts +6 -43
- package/dist/maintenance.d.ts.map +1 -1
- package/dist/maintenance.js +4 -67
- package/dist/maintenance.js.map +1 -1
- package/dist/media/library.d.ts +99 -0
- package/dist/media/library.d.ts.map +1 -0
- package/dist/media/library.js +160 -0
- package/dist/media/library.js.map +1 -0
- package/dist/media/uploads.d.ts +106 -0
- package/dist/media/uploads.d.ts.map +1 -0
- package/dist/media/uploads.js +192 -0
- package/dist/media/uploads.js.map +1 -0
- package/dist/micropub/content.js +1 -1
- package/dist/micropub/content.js.map +1 -1
- package/dist/micropub/create.d.ts +7 -15
- package/dist/micropub/create.d.ts.map +1 -1
- package/dist/micropub/create.js +48 -184
- package/dist/micropub/create.js.map +1 -1
- package/dist/micropub/endpoint.d.ts +2 -2
- package/dist/micropub/endpoint.d.ts.map +1 -1
- package/dist/micropub/endpoint.js +36 -47
- package/dist/micropub/endpoint.js.map +1 -1
- package/dist/micropub/media.d.ts +1 -3
- package/dist/micropub/media.d.ts.map +1 -1
- package/dist/micropub/media.js +10 -12
- package/dist/micropub/media.js.map +1 -1
- package/dist/micropub/update.d.ts +6 -14
- package/dist/micropub/update.d.ts.map +1 -1
- package/dist/micropub/update.js +37 -90
- package/dist/micropub/update.js.map +1 -1
- package/dist/notifications/comments.d.ts +8 -6
- package/dist/notifications/comments.d.ts.map +1 -1
- package/dist/notifications/comments.js +80 -76
- package/dist/notifications/comments.js.map +1 -1
- package/dist/notifications/digest.d.ts +6 -8
- package/dist/notifications/digest.d.ts.map +1 -1
- package/dist/notifications/digest.js +15 -26
- package/dist/notifications/digest.js.map +1 -1
- package/dist/notifications/index.d.ts +9 -6
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +6 -5
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/links.d.ts.map +1 -1
- package/dist/notifications/links.js +2 -2
- package/dist/notifications/links.js.map +1 -1
- package/dist/{mail/service.d.ts → notifications/mail-service.d.ts} +16 -20
- package/dist/notifications/mail-service.d.ts.map +1 -0
- package/dist/{mail/service.js → notifications/mail-service.js} +100 -121
- package/dist/notifications/mail-service.js.map +1 -0
- package/dist/{mail/templates.d.ts → notifications/mail-templates.d.ts} +4 -2
- package/dist/notifications/mail-templates.d.ts.map +1 -0
- package/dist/{mail/templates.js → notifications/mail-templates.js} +5 -3
- package/dist/notifications/mail-templates.js.map +1 -0
- package/dist/notifications/optouts.d.ts +0 -2
- package/dist/notifications/optouts.d.ts.map +1 -1
- package/dist/notifications/optouts.js +2 -4
- package/dist/notifications/optouts.js.map +1 -1
- package/dist/notifications/tokens.d.ts +2 -4
- package/dist/notifications/tokens.d.ts.map +1 -1
- package/dist/notifications/tokens.js +4 -30
- package/dist/notifications/tokens.js.map +1 -1
- package/dist/notify.d.ts +4 -2
- package/dist/notify.d.ts.map +1 -1
- package/dist/notify.js +35 -35
- package/dist/notify.js.map +1 -1
- package/dist/plugin.d.ts +59 -19
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +22 -4
- package/dist/plugin.js.map +1 -1
- package/dist/plugins/changes.d.ts +0 -1
- package/dist/plugins/changes.d.ts.map +1 -1
- package/dist/plugins/changes.js +2 -3
- package/dist/plugins/changes.js.map +1 -1
- package/dist/plugins/data.d.ts +19 -0
- package/dist/plugins/data.d.ts.map +1 -0
- package/dist/plugins/data.js +117 -0
- package/dist/plugins/data.js.map +1 -0
- package/dist/plugins/enabled.d.ts +1 -13
- package/dist/plugins/enabled.d.ts.map +1 -1
- package/dist/plugins/enabled.js +12 -26
- package/dist/plugins/enabled.js.map +1 -1
- package/dist/plugins/fetch.d.ts +1 -1
- package/dist/plugins/fetch.d.ts.map +1 -1
- package/dist/plugins/fetch.js +1 -1
- package/dist/plugins/fetch.js.map +1 -1
- package/dist/plugins/folder.d.ts.map +1 -1
- package/dist/plugins/folder.js +1 -3
- package/dist/plugins/folder.js.map +1 -1
- package/dist/plugins/install.d.ts +1 -1
- package/dist/plugins/install.d.ts.map +1 -1
- package/dist/plugins/install.js +14 -45
- package/dist/plugins/install.js.map +1 -1
- package/dist/plugins/manage.d.ts +1 -1
- package/dist/plugins/manage.d.ts.map +1 -1
- package/dist/plugins/migrate-settings.d.ts +7 -0
- package/dist/plugins/migrate-settings.d.ts.map +1 -0
- package/dist/plugins/migrate-settings.js +41 -0
- package/dist/plugins/migrate-settings.js.map +1 -0
- package/dist/plugins/registry.d.ts +10 -14
- package/dist/plugins/registry.d.ts.map +1 -1
- package/dist/plugins/registry.js +63 -45
- package/dist/plugins/registry.js.map +1 -1
- package/dist/plugins/settings.d.ts +5 -57
- package/dist/plugins/settings.d.ts.map +1 -1
- package/dist/plugins/settings.js +47 -181
- package/dist/plugins/settings.js.map +1 -1
- package/dist/plugins/site.d.ts +3 -2
- package/dist/plugins/site.d.ts.map +1 -1
- package/dist/plugins/site.js +8 -8
- package/dist/plugins/site.js.map +1 -1
- package/dist/plugins/upgrade.d.ts.map +1 -1
- package/dist/plugins/upgrade.js +4 -3
- package/dist/plugins/upgrade.js.map +1 -1
- package/dist/privacy/erase.d.ts +1 -1
- package/dist/privacy/erase.d.ts.map +1 -1
- package/dist/privacy/retention.d.ts +6 -2
- package/dist/privacy/retention.d.ts.map +1 -1
- package/dist/privacy/retention.js +20 -15
- package/dist/privacy/retention.js.map +1 -1
- package/dist/settings/akismet.d.ts +9 -0
- package/dist/settings/akismet.d.ts.map +1 -0
- package/dist/settings/akismet.js +46 -0
- package/dist/settings/akismet.js.map +1 -0
- package/dist/settings/declare.d.ts +15 -0
- package/dist/settings/declare.d.ts.map +1 -0
- package/dist/settings/declare.js +20 -0
- package/dist/settings/declare.js.map +1 -0
- package/dist/settings/dev-mode.d.ts +29 -0
- package/dist/settings/dev-mode.d.ts.map +1 -0
- package/dist/settings/dev-mode.js +77 -0
- package/dist/settings/dev-mode.js.map +1 -0
- package/dist/settings/kinds.d.ts +13 -0
- package/dist/settings/kinds.d.ts.map +1 -0
- package/dist/settings/kinds.js +108 -0
- package/dist/settings/kinds.js.map +1 -0
- package/dist/settings/mail.d.ts +10 -0
- package/dist/settings/mail.d.ts.map +1 -0
- package/dist/settings/mail.js +92 -0
- package/dist/settings/mail.js.map +1 -0
- package/dist/settings/maintenance.d.ts +6 -0
- package/dist/settings/maintenance.d.ts.map +1 -0
- package/dist/settings/maintenance.js +33 -0
- package/dist/settings/maintenance.js.map +1 -0
- package/dist/settings/names.d.ts +7 -0
- package/dist/settings/names.d.ts.map +1 -0
- package/dist/settings/names.js +31 -0
- package/dist/settings/names.js.map +1 -0
- package/dist/settings/presence.d.ts +2 -0
- package/dist/settings/presence.d.ts.map +1 -0
- package/dist/settings/presence.js +7 -0
- package/dist/settings/presence.js.map +1 -0
- package/dist/settings/registry.d.ts +14 -0
- package/dist/settings/registry.d.ts.map +1 -0
- package/dist/settings/registry.js +369 -0
- package/dist/settings/registry.js.map +1 -0
- package/dist/settings/types.d.ts +176 -0
- package/dist/settings/types.d.ts.map +1 -0
- package/dist/settings/types.js +2 -0
- package/dist/settings/types.js.map +1 -0
- package/dist/site/admin-paths.d.ts +23 -0
- package/dist/site/admin-paths.d.ts.map +1 -0
- package/dist/site/admin-paths.js +25 -0
- package/dist/site/admin-paths.js.map +1 -0
- package/dist/site/admin-theme.d.ts.map +1 -0
- package/dist/site/admin-theme.js.map +1 -0
- package/dist/{web → site}/authors.d.ts +1 -72
- package/dist/site/authors.d.ts.map +1 -0
- package/dist/site/authors.js +107 -0
- package/dist/site/authors.js.map +1 -0
- package/dist/site/feed-settings.d.ts +28 -0
- package/dist/site/feed-settings.d.ts.map +1 -0
- package/dist/site/feed-settings.js +22 -0
- package/dist/site/feed-settings.js.map +1 -0
- package/dist/{web/indexnow.d.ts → site/indexnow-key.d.ts} +1 -1
- package/dist/site/indexnow-key.d.ts.map +1 -0
- package/dist/{web/indexnow.js → site/indexnow-key.js} +1 -1
- package/dist/site/indexnow-key.js.map +1 -0
- package/dist/site/namespace.d.ts +88 -0
- package/dist/site/namespace.d.ts.map +1 -0
- package/dist/site/namespace.js +471 -0
- package/dist/site/namespace.js.map +1 -0
- package/dist/{web → site}/navigation.d.ts +7 -1
- package/dist/site/navigation.d.ts.map +1 -0
- package/dist/{web → site}/navigation.js +18 -0
- package/dist/site/navigation.js.map +1 -0
- package/dist/{notifications/preferences.d.ts → site/notification-preferences.d.ts} +2 -2
- package/dist/site/notification-preferences.d.ts.map +1 -0
- package/dist/{notifications/preferences.js → site/notification-preferences.js} +1 -1
- package/dist/site/notification-preferences.js.map +1 -0
- package/dist/site/passwords.d.ts.map +1 -0
- package/dist/site/passwords.js.map +1 -0
- package/dist/{privacy/policy.d.ts → site/retention-policy.d.ts} +1 -1
- package/dist/site/retention-policy.d.ts.map +1 -0
- package/dist/{privacy/policy.js → site/retention-policy.js} +1 -1
- package/dist/site/retention-policy.js.map +1 -0
- package/dist/site/robots.d.ts +62 -0
- package/dist/site/robots.d.ts.map +1 -0
- package/dist/site/robots.js +130 -0
- package/dist/site/robots.js.map +1 -0
- package/dist/site/setting-rows.d.ts +45 -0
- package/dist/site/setting-rows.d.ts.map +1 -0
- package/dist/site/setting-rows.js +361 -0
- package/dist/site/setting-rows.js.map +1 -0
- package/dist/site/setting-values.d.ts +63 -0
- package/dist/site/setting-values.d.ts.map +1 -0
- package/dist/site/setting-values.js +170 -0
- package/dist/site/setting-values.js.map +1 -0
- package/dist/site/settings-form.d.ts +30 -0
- package/dist/site/settings-form.d.ts.map +1 -0
- package/dist/site/settings-form.js +54 -0
- package/dist/site/settings-form.js.map +1 -0
- package/dist/site/settings.d.ts +29 -0
- package/dist/site/settings.d.ts.map +1 -0
- package/dist/site/settings.js +44 -0
- package/dist/site/settings.js.map +1 -0
- package/dist/site/site-json.d.ts +38 -0
- package/dist/site/site-json.d.ts.map +1 -0
- package/dist/site/site-json.js +69 -0
- package/dist/site/site-json.js.map +1 -0
- package/dist/{admin/accounts.d.ts → site/users.d.ts} +25 -50
- package/dist/site/users.d.ts.map +1 -0
- package/dist/{admin/accounts.js → site/users.js} +51 -166
- package/dist/site/users.js.map +1 -0
- package/dist/store/admin-store.d.ts +72 -0
- package/dist/store/admin-store.d.ts.map +1 -0
- package/dist/store/admin-store.js +62 -0
- package/dist/store/admin-store.js.map +1 -0
- package/dist/store/comments.d.ts +253 -0
- package/dist/store/comments.d.ts.map +1 -0
- package/dist/store/comments.js +234 -0
- package/dist/store/comments.js.map +1 -0
- package/dist/store/conversation.d.ts +28 -0
- package/dist/store/conversation.d.ts.map +1 -0
- package/dist/store/conversation.js +16 -0
- package/dist/store/conversation.js.map +1 -0
- package/dist/store/deliveries.d.ts +92 -0
- package/dist/store/deliveries.d.ts.map +1 -0
- package/dist/store/deliveries.js +88 -0
- package/dist/store/deliveries.js.map +1 -0
- package/dist/store/followers.d.ts +91 -0
- package/dist/store/followers.d.ts.map +1 -0
- package/dist/store/followers.js +79 -0
- package/dist/store/followers.js.map +1 -0
- package/dist/{federation/replies.d.ts → store/inbox-replies.d.ts} +2 -2
- package/dist/store/inbox-replies.d.ts.map +1 -0
- package/dist/{federation/replies.js → store/inbox-replies.js} +2 -4
- package/dist/store/inbox-replies.js.map +1 -0
- package/dist/store/inbox.d.ts +132 -0
- package/dist/store/inbox.d.ts.map +1 -0
- package/dist/store/inbox.js +157 -0
- package/dist/store/inbox.js.map +1 -0
- package/dist/store/profiles.d.ts +49 -0
- package/dist/store/profiles.d.ts.map +1 -0
- package/dist/store/profiles.js +71 -0
- package/dist/store/profiles.js.map +1 -0
- package/dist/store/queue-database.d.ts +11 -0
- package/dist/store/queue-database.d.ts.map +1 -0
- package/dist/store/queue-database.js +39 -0
- package/dist/store/queue-database.js.map +1 -0
- package/dist/store/relays.d.ts +66 -0
- package/dist/store/relays.d.ts.map +1 -0
- package/dist/store/relays.js +69 -0
- package/dist/store/relays.js.map +1 -0
- package/dist/store/rows.d.ts +33 -0
- package/dist/store/rows.d.ts.map +1 -0
- package/dist/store/rows.js +44 -0
- package/dist/store/rows.js.map +1 -0
- package/dist/store/schema.d.ts +14 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +588 -0
- package/dist/store/schema.js.map +1 -0
- package/dist/store/sessions.d.ts +90 -0
- package/dist/store/sessions.d.ts.map +1 -0
- package/dist/store/sessions.js +107 -0
- package/dist/store/sessions.js.map +1 -0
- package/dist/store/state.d.ts +18 -0
- package/dist/store/state.d.ts.map +1 -0
- package/dist/store/state.js +19 -0
- package/dist/store/state.js.map +1 -0
- package/dist/store/tokens.d.ts +75 -0
- package/dist/store/tokens.d.ts.map +1 -0
- package/dist/store/tokens.js +82 -0
- package/dist/store/tokens.js.map +1 -0
- package/dist/store/webmentions.d.ts +66 -0
- package/dist/store/webmentions.d.ts.map +1 -0
- package/dist/store/webmentions.js +84 -0
- package/dist/store/webmentions.js.map +1 -0
- package/dist/supervisor/control.d.ts +1 -0
- package/dist/supervisor/control.d.ts.map +1 -1
- package/dist/supervisor/control.js +10 -0
- package/dist/supervisor/control.js.map +1 -1
- package/dist/supervisor/primary.d.ts +1 -1
- package/dist/supervisor/primary.d.ts.map +1 -1
- package/dist/supervisor/primary.js +46 -14
- package/dist/supervisor/primary.js.map +1 -1
- package/dist/supervisor/worker.d.ts +6 -2
- package/dist/supervisor/worker.d.ts.map +1 -1
- package/dist/supervisor/worker.js +4 -4
- package/dist/supervisor/worker.js.map +1 -1
- package/dist/testing.d.ts +8 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +6 -0
- package/dist/testing.js.map +1 -0
- package/dist/{web → theme}/assets.d.ts +1 -32
- package/dist/theme/assets.d.ts.map +1 -0
- package/dist/{web → theme}/assets.js +2 -100
- package/dist/theme/assets.js.map +1 -0
- package/dist/theme/site-data.d.ts +187 -0
- package/dist/theme/site-data.d.ts.map +1 -0
- package/dist/theme/site-data.js +160 -0
- package/dist/theme/site-data.js.map +1 -0
- package/dist/{web → theme}/templates.d.ts +2 -0
- package/dist/theme/templates.d.ts.map +1 -0
- package/dist/{web → theme}/templates.js +9 -32
- package/dist/theme/templates.js.map +1 -0
- package/dist/{web → theme}/themes.d.ts +1 -0
- package/dist/theme/themes.d.ts.map +1 -0
- package/dist/{web → theme}/themes.js +6 -3
- package/dist/theme/themes.js.map +1 -0
- package/dist/util/client-id.d.ts.map +1 -0
- package/dist/util/client-id.js.map +1 -0
- package/dist/util/clock.d.ts +9 -0
- package/dist/util/clock.d.ts.map +1 -0
- package/dist/util/clock.js +2 -0
- package/dist/util/clock.js.map +1 -0
- package/dist/util/errors.d.ts +3 -0
- package/dist/util/errors.d.ts.map +1 -0
- package/dist/util/errors.js +5 -0
- package/dist/util/errors.js.map +1 -0
- package/dist/util/files.d.ts +5 -0
- package/dist/util/files.d.ts.map +1 -0
- package/dist/util/files.js +38 -0
- package/dist/util/files.js.map +1 -0
- package/dist/util/forms.d.ts +5 -0
- package/dist/util/forms.d.ts.map +1 -0
- package/dist/util/forms.js +17 -0
- package/dist/util/forms.js.map +1 -0
- package/dist/util/guards.d.ts +4 -0
- package/dist/util/guards.d.ts.map +1 -0
- package/dist/util/guards.js +12 -0
- package/dist/util/guards.js.map +1 -0
- package/dist/{webmention/html.d.ts → util/html-tree.d.ts} +2 -2
- package/dist/util/html-tree.d.ts.map +1 -0
- package/dist/{webmention/html.js → util/html-tree.js} +3 -64
- package/dist/util/html-tree.js.map +1 -0
- package/dist/util/html.d.ts +4 -0
- package/dist/util/html.d.ts.map +1 -0
- package/dist/util/html.js +34 -0
- package/dist/util/html.js.map +1 -0
- package/dist/util/public-address.d.ts.map +1 -0
- package/dist/util/public-address.js.map +1 -0
- package/dist/util/queue.d.ts +7 -0
- package/dist/util/queue.d.ts.map +1 -0
- package/dist/util/queue.js +23 -0
- package/dist/util/queue.js.map +1 -0
- package/dist/util/retry.d.ts +11 -0
- package/dist/util/retry.d.ts.map +1 -0
- package/dist/util/retry.js +14 -0
- package/dist/util/retry.js.map +1 -0
- package/dist/util/url.d.ts +11 -0
- package/dist/util/url.d.ts.map +1 -0
- package/dist/util/url.js +20 -0
- package/dist/util/url.js.map +1 -0
- package/dist/web/absolute-urls.js +1 -1
- package/dist/web/absolute-urls.js.map +1 -1
- package/dist/web/archive.js +2 -2
- package/dist/web/archive.js.map +1 -1
- package/dist/web/asset-routes.d.ts +52 -0
- package/dist/web/asset-routes.d.ts.map +1 -0
- package/dist/web/asset-routes.js +115 -0
- package/dist/web/asset-routes.js.map +1 -0
- package/dist/{avatars/routes.d.ts → web/avatar-routes.d.ts} +2 -2
- package/dist/web/avatar-routes.d.ts.map +1 -0
- package/dist/{avatars/routes.js → web/avatar-routes.js} +3 -3
- package/dist/web/avatar-routes.js.map +1 -0
- package/dist/web/comment-routes.d.ts +18 -0
- package/dist/web/comment-routes.d.ts.map +1 -0
- package/dist/web/comment-routes.js +177 -0
- package/dist/web/comment-routes.js.map +1 -0
- package/dist/web/compression.d.ts.map +1 -1
- package/dist/web/compression.js +1 -2
- package/dist/web/compression.js.map +1 -1
- package/dist/{contact/routes.d.ts → web/contact-routes.d.ts} +2 -2
- package/dist/web/contact-routes.d.ts.map +1 -0
- package/dist/{contact/routes.js → web/contact-routes.js} +15 -18
- package/dist/web/contact-routes.js.map +1 -0
- package/dist/web/context.d.ts +6 -187
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +13 -174
- package/dist/web/context.js.map +1 -1
- package/dist/web/conversation.d.ts +6 -357
- package/dist/web/conversation.d.ts.map +1 -1
- package/dist/web/conversation.js +177 -151
- package/dist/web/conversation.js.map +1 -1
- package/dist/web/document-page.d.ts +13 -0
- package/dist/web/document-page.d.ts.map +1 -0
- package/dist/web/document-page.js +137 -0
- package/dist/web/document-page.js.map +1 -0
- package/dist/web/document-site.d.ts +14 -0
- package/dist/web/document-site.d.ts.map +1 -0
- package/dist/web/document-site.js +29 -0
- package/dist/web/document-site.js.map +1 -0
- package/dist/web/embed.d.ts +13 -0
- package/dist/web/embed.d.ts.map +1 -0
- package/dist/web/embed.js +66 -0
- package/dist/web/embed.js.map +1 -0
- package/dist/{env.d.ts → web/env.d.ts} +36 -26
- package/dist/web/env.d.ts.map +1 -0
- package/dist/web/env.js.map +1 -0
- package/dist/web/federation-routes.d.ts +33 -0
- package/dist/web/federation-routes.d.ts.map +1 -0
- package/dist/web/federation-routes.js +282 -0
- package/dist/web/federation-routes.js.map +1 -0
- package/dist/web/feed-atom.d.ts.map +1 -1
- package/dist/web/feed-atom.js +4 -3
- package/dist/web/feed-atom.js.map +1 -1
- package/dist/web/feed-item.d.ts +4 -39
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +9 -73
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-json.js +2 -2
- package/dist/web/feed-json.js.map +1 -1
- package/dist/web/feed-routes.d.ts +35 -0
- package/dist/web/feed-routes.d.ts.map +1 -0
- package/dist/web/feed-routes.js +126 -0
- package/dist/web/feed-routes.js.map +1 -0
- package/dist/web/feed-rss.d.ts.map +1 -1
- package/dist/web/feed-rss.js +9 -6
- package/dist/web/feed-rss.js.map +1 -1
- package/dist/web/feed-source.d.ts +11 -41
- package/dist/web/feed-source.d.ts.map +1 -1
- package/dist/web/feed-source.js +11 -48
- package/dist/web/feed-source.js.map +1 -1
- package/dist/web/feeds.d.ts +4 -2
- package/dist/web/feeds.d.ts.map +1 -1
- package/dist/web/feeds.js +8 -5
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/health.d.ts +1 -1
- package/dist/web/health.d.ts.map +1 -1
- package/dist/web/health.js +1 -1
- package/dist/web/health.js.map +1 -1
- package/dist/web/index.d.ts +53 -29
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +37 -18
- package/dist/web/index.js.map +1 -1
- package/dist/{indieauth/activity-log.d.ts → web/indieauth-activity-log.d.ts} +3 -5
- package/dist/web/indieauth-activity-log.d.ts.map +1 -0
- package/dist/{indieauth/activity-log.js → web/indieauth-activity-log.js} +7 -10
- package/dist/web/indieauth-activity-log.js.map +1 -0
- package/dist/{indieauth/bearer.d.ts → web/indieauth-bearer.d.ts} +5 -5
- package/dist/web/indieauth-bearer.d.ts.map +1 -0
- package/dist/{indieauth/bearer.js → web/indieauth-bearer.js} +5 -5
- package/dist/web/indieauth-bearer.js.map +1 -0
- package/dist/{indieauth/discovery.d.ts → web/indieauth-discovery.d.ts} +2 -11
- package/dist/web/indieauth-discovery.d.ts.map +1 -0
- package/dist/{indieauth/discovery.js → web/indieauth-discovery.js} +7 -17
- package/dist/web/indieauth-discovery.js.map +1 -0
- package/dist/{indieauth/token.d.ts → web/indieauth-token.d.ts} +2 -2
- package/dist/web/indieauth-token.d.ts.map +1 -0
- package/dist/{indieauth/token.js → web/indieauth-token.js} +9 -9
- package/dist/web/indieauth-token.js.map +1 -0
- package/dist/web/listing-pages.d.ts +19 -0
- package/dist/web/listing-pages.d.ts.map +1 -0
- package/dist/web/listing-pages.js +147 -0
- package/dist/web/listing-pages.js.map +1 -0
- package/dist/web/listing-subject.d.ts +58 -0
- package/dist/web/listing-subject.d.ts.map +1 -0
- package/dist/web/listing-subject.js +89 -0
- package/dist/web/listing-subject.js.map +1 -0
- package/dist/web/llms.d.ts +16 -15
- package/dist/web/llms.d.ts.map +1 -1
- package/dist/web/llms.js +23 -35
- package/dist/web/llms.js.map +1 -1
- package/dist/web/maintenance.d.ts +1 -1
- package/dist/web/maintenance.d.ts.map +1 -1
- package/dist/web/maintenance.js +10 -16
- package/dist/web/maintenance.js.map +1 -1
- package/dist/web/manifest.d.ts +1 -1
- package/dist/web/manifest.d.ts.map +1 -1
- package/dist/web/manifest.js +2 -1
- package/dist/web/manifest.js.map +1 -1
- package/dist/web/markdown-page.d.ts +76 -0
- package/dist/web/markdown-page.d.ts.map +1 -0
- package/dist/web/markdown-page.js +79 -0
- package/dist/web/markdown-page.js.map +1 -0
- package/dist/web/markdown-representation.d.ts +15 -0
- package/dist/web/markdown-representation.d.ts.map +1 -0
- package/dist/web/markdown-representation.js +130 -0
- package/dist/web/markdown-representation.js.map +1 -0
- package/dist/web/negotiate.d.ts +5 -12
- package/dist/web/negotiate.d.ts.map +1 -1
- package/dist/web/negotiate.js +11 -18
- package/dist/web/negotiate.js.map +1 -1
- package/dist/{notifications/routes.d.ts → web/notification-routes.d.ts} +2 -2
- package/dist/web/notification-routes.d.ts.map +1 -0
- package/dist/{notifications/routes.js → web/notification-routes.js} +24 -29
- package/dist/web/notification-routes.js.map +1 -0
- package/dist/web/oembed.d.ts +2 -2
- package/dist/web/oembed.d.ts.map +1 -1
- package/dist/web/oembed.js +5 -4
- package/dist/web/oembed.js.map +1 -1
- package/dist/web/opensearch.d.ts.map +1 -1
- package/dist/web/opensearch.js +2 -1
- package/dist/web/opensearch.js.map +1 -1
- package/dist/web/page-sources.d.ts +14 -0
- package/dist/web/page-sources.d.ts.map +1 -0
- package/dist/web/page-sources.js +43 -0
- package/dist/web/page-sources.js.map +1 -0
- package/dist/web/pagination.d.ts +1 -0
- package/dist/web/pagination.d.ts.map +1 -1
- package/dist/web/pagination.js +4 -1
- package/dist/web/pagination.js.map +1 -1
- package/dist/web/paths.d.ts +52 -0
- package/dist/web/paths.d.ts.map +1 -0
- package/dist/web/paths.js +208 -0
- package/dist/web/paths.js.map +1 -0
- package/dist/web/pingback-routes.d.ts +5 -0
- package/dist/web/pingback-routes.d.ts.map +1 -0
- package/dist/web/pingback-routes.js +100 -0
- package/dist/web/pingback-routes.js.map +1 -0
- package/dist/{plugins/federation.d.ts → web/plugin-federation.d.ts} +3 -3
- package/dist/web/plugin-federation.d.ts.map +1 -0
- package/dist/{plugins/federation.js → web/plugin-federation.js} +55 -45
- package/dist/web/plugin-federation.js.map +1 -0
- package/dist/{plugins/mount.d.ts → web/plugin-routes.d.ts} +3 -3
- package/dist/web/plugin-routes.d.ts.map +1 -0
- package/dist/{plugins/mount.js → web/plugin-routes.js} +2 -2
- package/dist/web/plugin-routes.js.map +1 -0
- package/dist/web/redirects.d.ts +1 -84
- package/dist/web/redirects.d.ts.map +1 -1
- package/dist/web/redirects.js +0 -232
- package/dist/web/redirects.js.map +1 -1
- package/dist/web/render.d.ts +55 -12
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +123 -65
- package/dist/web/render.js.map +1 -1
- package/dist/web/request.d.ts +44 -0
- package/dist/web/request.d.ts.map +1 -0
- package/dist/web/request.js +82 -0
- package/dist/web/request.js.map +1 -0
- package/dist/web/robots.d.ts +1 -46
- package/dist/web/robots.d.ts.map +1 -1
- package/dist/web/robots.js +5 -131
- package/dist/web/robots.js.map +1 -1
- package/dist/web/routes.d.ts +5 -39
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +102 -1374
- package/dist/web/routes.js.map +1 -1
- package/dist/web/search.d.ts.map +1 -1
- package/dist/web/search.js +2 -9
- package/dist/web/search.js.map +1 -1
- package/dist/{admin → web}/session.d.ts +1 -5
- package/dist/web/session.d.ts.map +1 -0
- package/dist/{admin → web}/session.js +0 -4
- package/dist/web/session.js.map +1 -0
- package/dist/web/share-image.d.ts.map +1 -1
- package/dist/web/share-image.js +3 -2
- package/dist/web/share-image.js.map +1 -1
- package/dist/web/signed-in.d.ts +27 -0
- package/dist/web/signed-in.d.ts.map +1 -0
- package/dist/web/signed-in.js +45 -0
- package/dist/web/signed-in.js.map +1 -0
- package/dist/web/site-index.d.ts +13 -0
- package/dist/web/site-index.d.ts.map +1 -0
- package/dist/web/site-index.js +142 -0
- package/dist/web/site-index.js.map +1 -0
- package/dist/web/site-view.d.ts +43 -0
- package/dist/web/site-view.d.ts.map +1 -0
- package/dist/web/site-view.js +65 -0
- package/dist/web/site-view.js.map +1 -0
- package/dist/web/sitemap.d.ts.map +1 -1
- package/dist/web/sitemap.js +5 -7
- package/dist/web/sitemap.js.map +1 -1
- package/dist/{admin → web}/throttle.d.ts +3 -4
- package/dist/web/throttle.d.ts.map +1 -0
- package/dist/{admin → web}/throttle.js +1 -2
- package/dist/web/throttle.js.map +1 -0
- package/dist/web/webmention-routes.d.ts +11 -0
- package/dist/web/webmention-routes.d.ts.map +1 -0
- package/dist/web/webmention-routes.js +51 -0
- package/dist/web/webmention-routes.js.map +1 -0
- package/dist/web/well-known.d.ts +2 -2
- package/dist/web/well-known.d.ts.map +1 -1
- package/dist/web/well-known.js +10 -20
- package/dist/web/well-known.js.map +1 -1
- package/dist/webmention/cited-picture.d.ts +1 -2
- package/dist/webmention/cited-picture.d.ts.map +1 -1
- package/dist/webmention/cited-picture.js +5 -7
- package/dist/webmention/cited-picture.js.map +1 -1
- package/dist/webmention/discovery.d.ts +2 -13
- package/dist/webmention/discovery.d.ts.map +1 -1
- package/dist/webmention/discovery.js +18 -73
- package/dist/webmention/discovery.js.map +1 -1
- package/dist/webmention/endpoint.d.ts +27 -0
- package/dist/webmention/endpoint.d.ts.map +1 -0
- package/dist/webmention/endpoint.js +26 -0
- package/dist/webmention/endpoint.js.map +1 -0
- package/dist/webmention/fetch-public.d.ts +18 -1
- package/dist/webmention/fetch-public.d.ts.map +1 -1
- package/dist/webmention/fetch-public.js +57 -23
- package/dist/webmention/fetch-public.js.map +1 -1
- package/dist/webmention/index.d.ts +11 -9
- package/dist/webmention/index.d.ts.map +1 -1
- package/dist/webmention/index.js +7 -6
- package/dist/webmention/index.js.map +1 -1
- package/dist/webmention/links.d.ts +0 -55
- package/dist/webmention/links.d.ts.map +1 -1
- package/dist/webmention/links.js +2 -185
- package/dist/webmention/links.js.map +1 -1
- package/dist/webmention/microformats.d.ts +1 -1
- package/dist/webmention/microformats.d.ts.map +1 -1
- package/dist/webmention/microformats.js +1 -1
- package/dist/webmention/microformats.js.map +1 -1
- package/dist/webmention/pingback.d.ts +13 -12
- package/dist/webmention/pingback.d.ts.map +1 -1
- package/dist/webmention/pingback.js +6 -113
- package/dist/webmention/pingback.js.map +1 -1
- package/dist/webmention/receive.d.ts +9 -4
- package/dist/webmention/receive.d.ts.map +1 -1
- package/dist/webmention/receive.js +35 -41
- package/dist/webmention/receive.js.map +1 -1
- package/dist/webmention/reply-context/fediverse.d.ts +29 -0
- package/dist/webmention/reply-context/fediverse.d.ts.map +1 -0
- package/dist/webmention/reply-context/fediverse.js +59 -0
- package/dist/webmention/reply-context/fediverse.js.map +1 -0
- package/dist/webmention/reply-context/json-ld.d.ts +5 -0
- package/dist/webmention/reply-context/json-ld.d.ts.map +1 -0
- package/dist/webmention/reply-context/json-ld.js +78 -0
- package/dist/webmention/reply-context/json-ld.js.map +1 -0
- package/dist/webmention/reply-context/oembed.d.ts +21 -0
- package/dist/webmention/reply-context/oembed.d.ts.map +1 -0
- package/dist/webmention/reply-context/oembed.js +137 -0
- package/dist/webmention/reply-context/oembed.js.map +1 -0
- package/dist/webmention/reply-context/page.d.ts +30 -0
- package/dist/webmention/reply-context/page.d.ts.map +1 -0
- package/dist/webmention/reply-context/page.js +134 -0
- package/dist/webmention/reply-context/page.js.map +1 -0
- package/dist/webmention/reply-context/source.d.ts +29 -0
- package/dist/webmention/reply-context/source.d.ts.map +1 -0
- package/dist/webmention/reply-context/source.js +46 -0
- package/dist/webmention/reply-context/source.js.map +1 -0
- package/dist/webmention/reply-context.d.ts +5 -34
- package/dist/webmention/reply-context.d.ts.map +1 -1
- package/dist/webmention/reply-context.js +9 -432
- package/dist/webmention/reply-context.js.map +1 -1
- package/dist/webmention/reply-contexts.d.ts +17 -6
- package/dist/webmention/reply-contexts.d.ts.map +1 -1
- package/dist/webmention/reply-contexts.js +19 -20
- package/dist/webmention/reply-contexts.js.map +1 -1
- package/dist/webmention/routes.d.ts +1 -1
- package/dist/webmention/routes.d.ts.map +1 -1
- package/dist/webmention/routes.js +5 -8
- package/dist/webmention/routes.js.map +1 -1
- package/dist/webmention/service.d.ts +9 -7
- package/dist/webmention/service.d.ts.map +1 -1
- package/dist/webmention/service.js +111 -110
- package/dist/webmention/service.js.map +1 -1
- package/dist/webmention/syndication.d.ts +3 -26
- package/dist/webmention/syndication.d.ts.map +1 -1
- package/dist/webmention/syndication.js +6 -20
- package/dist/webmention/syndication.js.map +1 -1
- package/package.json +17 -11
- package/themes/default/README.md +36 -2339
- package/themes/default/layouts/base.njk +5 -4
- package/themes/default/layouts/front-page.njk +4 -3
- package/themes/default/layouts/page.njk +2 -0
- package/themes/default/partials/comment-form.njk +1 -1
- package/themes/default/partials/contact-form.njk +1 -2
- package/themes/default/partials/conversation.njk +1 -2
- package/themes/default/partials/markdown-footer.md.njk +24 -0
- package/themes/default/partials/markdown-header.md.njk +31 -0
- package/themes/default/partials/post-list.njk +11 -11
- package/themes/default/src/style.css +18 -6
- package/themes/default/static/style.css +19 -0
- package/dist/admin/accounts.d.ts.map +0 -1
- package/dist/admin/accounts.js.map +0 -1
- package/dist/admin/admin-theme.d.ts.map +0 -1
- package/dist/admin/admin-theme.js.map +0 -1
- package/dist/admin/enclosure-field.d.ts.map +0 -1
- package/dist/admin/enclosure-field.js.map +0 -1
- package/dist/admin/event-field.d.ts.map +0 -1
- package/dist/admin/event-field.js.map +0 -1
- package/dist/admin/location-field.d.ts +0 -27
- package/dist/admin/location-field.d.ts.map +0 -1
- package/dist/admin/location-field.js.map +0 -1
- package/dist/admin/passwords.d.ts.map +0 -1
- package/dist/admin/passwords.js.map +0 -1
- package/dist/admin/photo-field.d.ts +0 -38
- package/dist/admin/photo-field.d.ts.map +0 -1
- package/dist/admin/photo-field.js.map +0 -1
- package/dist/admin/read-field.d.ts +0 -22
- package/dist/admin/read-field.d.ts.map +0 -1
- package/dist/admin/read-field.js +0 -61
- package/dist/admin/read-field.js.map +0 -1
- package/dist/admin/session.d.ts.map +0 -1
- package/dist/admin/session.js.map +0 -1
- package/dist/admin/store.d.ts +0 -1025
- package/dist/admin/store.d.ts.map +0 -1
- package/dist/admin/store.js +0 -1628
- package/dist/admin/store.js.map +0 -1
- package/dist/admin/throttle.d.ts.map +0 -1
- package/dist/admin/throttle.js.map +0 -1
- package/dist/avatars/routes.d.ts.map +0 -1
- package/dist/avatars/routes.js.map +0 -1
- package/dist/contact/routes.d.ts.map +0 -1
- package/dist/contact/routes.js.map +0 -1
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js.map +0 -1
- package/dist/federation/replies.d.ts.map +0 -1
- package/dist/federation/replies.js.map +0 -1
- package/dist/images/alt-text.d.ts.map +0 -1
- package/dist/images/alt-text.js.map +0 -1
- package/dist/indieauth/activity-log.d.ts.map +0 -1
- package/dist/indieauth/activity-log.js.map +0 -1
- package/dist/indieauth/app-activity.d.ts.map +0 -1
- package/dist/indieauth/app-activity.js.map +0 -1
- package/dist/indieauth/bearer.d.ts.map +0 -1
- package/dist/indieauth/bearer.js.map +0 -1
- package/dist/indieauth/client-id.d.ts.map +0 -1
- package/dist/indieauth/client-id.js.map +0 -1
- package/dist/indieauth/connected-apps.d.ts.map +0 -1
- package/dist/indieauth/connected-apps.js.map +0 -1
- package/dist/indieauth/consent.d.ts.map +0 -1
- package/dist/indieauth/consent.js.map +0 -1
- package/dist/indieauth/discovery.d.ts.map +0 -1
- package/dist/indieauth/discovery.js.map +0 -1
- package/dist/indieauth/token.d.ts.map +0 -1
- package/dist/indieauth/token.js.map +0 -1
- package/dist/mail/service.d.ts.map +0 -1
- package/dist/mail/service.js.map +0 -1
- package/dist/mail/templates.d.ts.map +0 -1
- package/dist/mail/templates.js.map +0 -1
- package/dist/notifications/preferences.d.ts.map +0 -1
- package/dist/notifications/preferences.js.map +0 -1
- package/dist/notifications/routes.d.ts.map +0 -1
- package/dist/notifications/routes.js.map +0 -1
- package/dist/plugins/federation.d.ts.map +0 -1
- package/dist/plugins/federation.js.map +0 -1
- package/dist/plugins/mount.d.ts.map +0 -1
- package/dist/plugins/mount.js.map +0 -1
- package/dist/privacy/policy.d.ts.map +0 -1
- package/dist/privacy/policy.js.map +0 -1
- package/dist/web/admin-bar.d.ts.map +0 -1
- package/dist/web/admin-bar.js.map +0 -1
- package/dist/web/assets.d.ts.map +0 -1
- package/dist/web/assets.js.map +0 -1
- package/dist/web/authors.d.ts.map +0 -1
- package/dist/web/authors.js +0 -211
- package/dist/web/authors.js.map +0 -1
- package/dist/web/documents.d.ts +0 -81
- package/dist/web/documents.d.ts.map +0 -1
- package/dist/web/documents.js.map +0 -1
- package/dist/web/errors.d.ts.map +0 -1
- package/dist/web/errors.js.map +0 -1
- package/dist/web/guids.d.ts.map +0 -1
- package/dist/web/guids.js.map +0 -1
- package/dist/web/indexnow.d.ts.map +0 -1
- package/dist/web/indexnow.js.map +0 -1
- package/dist/web/license.d.ts.map +0 -1
- package/dist/web/license.js.map +0 -1
- package/dist/web/locale.d.ts.map +0 -1
- package/dist/web/locale.js.map +0 -1
- package/dist/web/navigation.d.ts.map +0 -1
- package/dist/web/navigation.js.map +0 -1
- package/dist/web/recent.d.ts +0 -43
- package/dist/web/recent.d.ts.map +0 -1
- package/dist/web/recent.js +0 -37
- package/dist/web/recent.js.map +0 -1
- package/dist/web/sanitize.d.ts +0 -29
- package/dist/web/sanitize.d.ts.map +0 -1
- package/dist/web/sanitize.js.map +0 -1
- package/dist/web/taxonomy.d.ts.map +0 -1
- package/dist/web/taxonomy.js.map +0 -1
- package/dist/web/templates.d.ts.map +0 -1
- package/dist/web/templates.js.map +0 -1
- package/dist/web/themes.d.ts.map +0 -1
- package/dist/web/themes.js.map +0 -1
- package/dist/webmention/html.d.ts.map +0 -1
- package/dist/webmention/html.js.map +0 -1
- package/dist/webmention/public-address.d.ts.map +0 -1
- package/dist/webmention/public-address.js.map +0 -1
- /package/dist/{web → content}/license.d.ts +0 -0
- /package/dist/{web → content}/license.js +0 -0
- /package/dist/{admin → site}/admin-theme.d.ts +0 -0
- /package/dist/{admin → site}/admin-theme.js +0 -0
- /package/dist/{admin → site}/passwords.d.ts +0 -0
- /package/dist/{admin → site}/passwords.js +0 -0
- /package/dist/{indieauth → util}/client-id.d.ts +0 -0
- /package/dist/{indieauth → util}/client-id.js +0 -0
- /package/dist/{webmention → util}/public-address.d.ts +0 -0
- /package/dist/{webmention → util}/public-address.js +0 -0
- /package/dist/{env.js → web/env.js} +0 -0
package/themes/default/README.md
CHANGED
|
@@ -1,2341 +1,38 @@
|
|
|
1
1
|
# Default theme
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
comment-form.njk the form under a post or page that is taking comments
|
|
40
|
-
contact-form.njk the form on a page whose front matter says contact: true
|
|
41
|
-
archive.njk every post by month, on a page that says archive: true
|
|
42
|
-
backlinks.njk the site's own posts and pages that link to this one
|
|
43
|
-
search-form.njk the search box, on the search page
|
|
44
|
-
head-end.njk empty: what a site adds to the end of every <head>
|
|
45
|
-
body-end.njk empty: what a site adds to the end of every <body>
|
|
46
|
-
mail/
|
|
47
|
-
test.*.njk the Send test email message
|
|
48
|
-
password-reset.*.njk the forgot-password link
|
|
49
|
-
password-changed.*.njk the notice sent once a password has been set
|
|
50
|
-
comment-pending.*.njk the moderation notice
|
|
51
|
-
comment-digest.*.njk the hourly or daily digest of what is waiting
|
|
52
|
-
comment-reply.*.njk the notice a commenter gets about a reply
|
|
53
|
-
contact-message.*.njk a message from a page's contact form
|
|
54
|
-
src/
|
|
55
|
-
style.css the stylesheet's source, compiled with Tailwind
|
|
56
|
-
static/
|
|
57
|
-
style.css the compiled stylesheet, served at /theme/style.css
|
|
58
|
-
highlight.js the code highlighter
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Plain Nunjucks templates and one stylesheet. The stylesheet is written in
|
|
62
|
-
Tailwind v4 (`src/style.css`) and compiled when the package is built, so
|
|
63
|
-
`static/style.css` is a build product: gitignored in this repository, written
|
|
64
|
-
by `pnpm build` (which the root `prepare` runs after an install and before a
|
|
65
|
-
publish), and shipped in the package like every other file here. The compile
|
|
66
|
-
happens once, on the machine that builds the package. A reader gets one plain
|
|
67
|
-
CSS file and no script, web font or CDN request because of it.
|
|
68
|
-
|
|
69
|
-
The design is Paper (doc-9, decision-22): one warm column, serif throughout,
|
|
70
|
-
sans for small labels, and a kicker above every entry naming its kind.
|
|
71
|
-
|
|
72
|
-
**Rebuilding the stylesheet.** After editing `src/style.css`:
|
|
73
|
-
|
|
74
|
-
```
|
|
75
|
-
pnpm --filter @geekity/cms build:theme
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Tailwind scans only this theme's `layouts/` and `partials/`, so a utility class
|
|
79
|
-
written in a template here is compiled in and nothing outside the theme is
|
|
80
|
-
read. The templates themselves use the semantic class names below and the look
|
|
81
|
-
lives in `src/style.css`, through `@apply`.
|
|
82
|
-
|
|
83
|
-
**A site theme owes Tailwind nothing.** Its own `static/style.css` replaces this
|
|
84
|
-
one wholesale, and any CSS that knows the class names in this README will do.
|
|
85
|
-
The demo theme's is plain CSS. A site that only wants different colours
|
|
86
|
-
overrides the `--color-*` custom properties instead; see [Colours](#colours).
|
|
87
|
-
|
|
88
|
-
## The manifest
|
|
89
|
-
|
|
90
|
-
`theme.json` is what makes a directory a theme:
|
|
91
|
-
|
|
92
|
-
```json
|
|
93
|
-
{
|
|
94
|
-
"name": "Default",
|
|
95
|
-
"kind": "site",
|
|
96
|
-
"description": "One line about the theme.",
|
|
97
|
-
"colorScheme": "light dark",
|
|
98
|
-
"themeColor": { "light": "#faf7f2", "dark": "#171412" }
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
`name` is what a person sees, `kind` is `site` — the only kind there is, and
|
|
103
|
-
the field exists so another can be added later without the format changing —
|
|
104
|
-
and `description` is optional. The directory name is the theme's id. A
|
|
105
|
-
directory without a readable manifest, or one naming a kind this CMS does not
|
|
106
|
-
have, is not a theme.
|
|
107
|
-
|
|
108
|
-
`colorScheme` and `themeColor` are the colours the head declares before the
|
|
109
|
-
stylesheet loads (see [The head](#the-head)). `colorScheme` is one of `normal`,
|
|
110
|
-
`light`, `dark`, `light dark`, `dark light` or `only light`, preferred scheme
|
|
111
|
-
first. `themeColor` is the colour of the browser around the page, one per
|
|
112
|
-
scheme, as a three- or six-digit hex colour with no alpha. Make it the page's
|
|
113
|
-
own background. A value outside those is ignored, and so is one a theme leaves
|
|
114
|
-
out: each falls back to this theme's, just as a template does.
|
|
115
|
-
|
|
116
|
-
## Overriding a template
|
|
117
|
-
|
|
118
|
-
A site keeps its themes under one directory, `themes/` by default and
|
|
119
|
-
`themesDir` in the config, with one folder per theme and a `theme.json` in
|
|
120
|
-
each. Which one it wears is one setting, `theme` in `content/_data/site.json`,
|
|
121
|
-
holding the folder's name:
|
|
122
|
-
|
|
123
|
-
```
|
|
124
|
-
themes/
|
|
125
|
-
midnight/
|
|
126
|
-
theme.json
|
|
127
|
-
layouts/post.njk
|
|
128
|
-
static/style.css
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
```json
|
|
132
|
-
{ "theme": "midnight" }
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
The setting is what **Appearance > Themes** in the admin writes: the screen
|
|
136
|
-
lists this theme and every folder under `themes/`, marks the one in use, and
|
|
137
|
-
Activate on another writes its name — or, on the packaged theme, takes the key
|
|
138
|
-
out again. A change takes effect on the next request, with no restart, and a
|
|
139
|
-
folder whose manifest will not read is listed under "Not themes" with the
|
|
140
|
-
reason rather than quietly left out.
|
|
141
|
-
|
|
142
|
-
Resolution order for any template is then that theme first, this directory
|
|
143
|
-
second, one file at a time. The theme above replaces the post layout and keeps
|
|
144
|
-
receiving updates to every other template, and `static/style.css` replaces the
|
|
145
|
-
stylesheet the same way — assets under `/theme/` resolve in the same order.
|
|
146
|
-
Link a theme file with the `asset` filter rather than a literal `/theme/`
|
|
147
|
-
path: `{{ "style.css" | asset }}` puts a hash of the file in the URL, so a
|
|
148
|
-
reader's browser keeps it for a year and still fetches the new one the moment
|
|
149
|
-
it changes. The plain `/theme/style.css` keeps working, cached for an hour. A
|
|
150
|
-
site that has chosen no theme reads this one and nothing else, and a theme
|
|
151
|
-
sitting in `themes/` that the setting does not name is never on the path at
|
|
152
|
-
all. A `theme` naming a folder that is not there, or is not a theme, falls back
|
|
153
|
-
to this one with a warning in the log rather than a broken site.
|
|
154
|
-
|
|
155
|
-
The admin is not a theme and has no setting. Its templates live in their own
|
|
156
|
-
tree with a loader of their own, off this search path entirely, so no theme can
|
|
157
|
-
shadow the login form or the CSRF field inside it.
|
|
158
|
-
|
|
159
|
-
An override is a normal Nunjucks template, so it can extend or include the
|
|
160
|
-
packaged ones by name:
|
|
161
|
-
|
|
162
|
-
```njk
|
|
163
|
-
{% extends "layouts/base.njk" %}
|
|
164
|
-
|
|
165
|
-
{% block content %}
|
|
166
|
-
<article>
|
|
167
|
-
<h1>{{ title }}</h1>
|
|
168
|
-
{{ content | safe }}
|
|
169
|
-
</article>
|
|
170
|
-
{% endblock %}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
`layouts/base.njk` defines the blocks `title`, `speculationRules`,
|
|
174
|
-
`viewTransitions`, `head`, `alternates`, `header`, `breadcrumbs`, `content`,
|
|
175
|
-
`footer` and `scripts`, so most sites never have to copy it.
|
|
176
|
-
|
|
177
|
-
Two layouts are named for the pages the Reading settings pick:
|
|
178
|
-
|
|
179
|
-
| Template | Rendered for | Shipped here | Falls back to |
|
|
180
|
-
| ------------------------ | ----------------------------- | ------------ | ------------------ |
|
|
181
|
-
| `layouts/front-page.njk` | the page served at `/` | yes | `layouts/page.njk` |
|
|
182
|
-
| `layouts/posts-page.njk` | the listing on the posts page | no | `layouts/home.njk` |
|
|
183
|
-
|
|
184
|
-
`layouts/front-page.njk` is a layout here rather than only an override point:
|
|
185
|
-
see [The front page](#the-front-page) for what it draws. Overriding it is how a
|
|
186
|
-
front page is laid out differently from every other page without overriding the
|
|
187
|
-
layout they all use, and the fallback above is reached only by a theme that
|
|
188
|
-
replaced it with nothing.
|
|
189
|
-
|
|
190
|
-
There is no posts page layout: a site that sets one gets the listing layout
|
|
191
|
-
until it writes one. On the posts page the page's own front matter and rendered
|
|
192
|
-
body are on the context beside the listing, so `{{ content | safe }}` prints its
|
|
193
|
-
words above the posts; `layouts/home.njk` already does.
|
|
194
|
-
|
|
195
|
-
## Adding to every page
|
|
196
|
-
|
|
197
|
-
Analytics, a site verification tag or a chat widget goes on every page, and
|
|
198
|
-
none of them needs a layout copied. `layouts/base.njk` includes two partials
|
|
199
|
-
that this theme ships empty:
|
|
200
|
-
|
|
201
|
-
| Partial | Printed |
|
|
202
|
-
| ----------------------- | -------------------------------------------------- |
|
|
203
|
-
| `partials/head-end.njk` | last in `<head>`, after the cards and the JSON-LD |
|
|
204
|
-
| `partials/body-end.njk` | last in `<body>`, after the footer and the scripts |
|
|
205
|
-
|
|
206
|
-
A site theme that holds one of them, and nothing else but its `theme.json`,
|
|
207
|
-
adds that markup to every public HTML page: posts, pages, listings, search, the
|
|
208
|
-
front page, and the 404, 410, 500 and 503 pages. Every other template still
|
|
209
|
-
comes from this directory, so the site keeps receiving updates to them, and
|
|
210
|
-
the two partial names stay as they are. The includes sit outside every block,
|
|
211
|
-
so a layout that overrides `head` or `scripts` without `super()` keeps them.
|
|
212
|
-
|
|
213
|
-
They are part of the HTML page and nothing else. The Markdown, plain text and
|
|
214
|
-
JSON representations of a page, the feeds, `llms.txt`, mail and the admin do
|
|
215
|
-
not print them. The editor's preview renders the post or page layout in the
|
|
216
|
-
admin, so the context there carries `editorPreview: true` and `layouts/base.njk`
|
|
217
|
-
leaves both partials out. A theme that replaces `layouts/base.njk` and includes
|
|
218
|
-
them itself should test the same key.
|
|
219
|
-
|
|
220
|
-
Umami and Google Analytics 4, in `themes/analytics/`:
|
|
221
|
-
|
|
222
|
-
```
|
|
223
|
-
themes/analytics/
|
|
224
|
-
theme.json
|
|
225
|
-
partials/head-end.njk
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
```json
|
|
229
|
-
{ "name": "Analytics", "kind": "site" }
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
```njk
|
|
233
|
-
<script defer src="https://cloud.umami.is/script.js" data-website-id="your-website-id"></script>
|
|
234
|
-
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
|
|
235
|
-
<script>
|
|
236
|
-
window.dataLayer = window.dataLayer || [];
|
|
237
|
-
function gtag(){dataLayer.push(arguments);}
|
|
238
|
-
gtag('js', new Date());
|
|
239
|
-
gtag('config', 'G-XXXXXXXXXX');
|
|
240
|
-
</script>
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
Then wear the theme, either with Activate on **Appearance > Themes** or with
|
|
244
|
-
`"theme": "analytics"` in `content/_data/site.json`. A site that already wears
|
|
245
|
-
a theme of its own puts the partial in that theme instead.
|
|
246
|
-
|
|
247
|
-
The partial is a template, rendered with the page's context, so it can read a
|
|
248
|
-
value out of `site.json`, as in `data-website-id="{{ site.umamiId }}"`.
|
|
249
|
-
|
|
250
|
-
**In Docker.** The image reads themes from `/site/themes`. Mount the site's
|
|
251
|
-
`themes/` directory there, read-only, which is the commented line in
|
|
252
|
-
`deploy/compose.yaml`:
|
|
253
|
-
|
|
254
|
-
```yaml
|
|
255
|
-
volumes:
|
|
256
|
-
- ./themes:/site/themes:ro
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
No custom image is needed. A change to the partial shows on the next request.
|
|
260
|
-
|
|
261
|
-
**Under a Content-Security-Policy.** The default headers set only
|
|
262
|
-
`frame-ancestors`, so they restrict nothing a page loads, and the inline gtag
|
|
263
|
-
configuration runs as it is. A site that sets a policy of its own with
|
|
264
|
-
`securityHeaders` must allow what the snippets load and where they send data:
|
|
265
|
-
|
|
266
|
-
| Directive | Umami | Google Analytics 4 |
|
|
267
|
-
| ------------- | -------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
268
|
-
| `script-src` | `https://cloud.umami.is` | `https://*.googletagmanager.com`, and the inline configuration (see below) |
|
|
269
|
-
| `connect-src` | `https://gateway.umami.is` | `https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com` |
|
|
270
|
-
| `img-src` | none | `https://*.google-analytics.com https://*.googletagmanager.com` |
|
|
271
|
-
|
|
272
|
-
The inline `<script>` that configures gtag needs one of these:
|
|
273
|
-
|
|
274
|
-
- Move it to a file in the theme, `themes/analytics/static/gtag.js`, and load
|
|
275
|
-
it with `<script src="{{ "gtag.js" | asset }}"></script>`. It is then served
|
|
276
|
-
from the site, and `script-src 'self'` covers it.
|
|
277
|
-
- Allow its hash, `'sha256-…'`, in `script-src`. The hash covers every byte
|
|
278
|
-
between the tags, so it changes when the measurement ID or the whitespace
|
|
279
|
-
changes.
|
|
280
|
-
- Allow `'unsafe-inline'` in `script-src`. This also allows any injected
|
|
281
|
-
inline script, so prefer one of the other two.
|
|
282
|
-
|
|
283
|
-
## The page shell
|
|
284
|
-
|
|
285
|
-
`layouts/base.njk` is the page shell: the skip link, one `.global-wrapper` at the
|
|
286
|
-
42rem measure, `.global-header`, `<main id="main">` and the footer, in that
|
|
287
|
-
order. The shape is the andrewshell.org design's (decision-16) and the look is
|
|
288
|
-
Paper's (doc-9).
|
|
289
|
-
|
|
290
|
-
**One home for each kind of link.** The header carries the site menu, on every
|
|
291
|
-
page. [The bio](#the-bio) carries the person whose page or post it is. The
|
|
292
|
-
footer carries the site's own links. The search box is on the search page and
|
|
293
|
-
nowhere else.
|
|
294
|
-
|
|
295
|
-
**The header has one rule, and the menu under it.** On the front page it is the
|
|
296
|
-
site title as `h1.main-heading`, linked home, with `site.tagline` in a
|
|
297
|
-
`p.site-tagline` under it; on every other page it is `a.header-link-home`, the site
|
|
298
|
-
title small and linked home, and no tagline. The wrapper carries
|
|
299
|
-
`data-is-root-path="true"` at `/` and nothing anywhere else, which is how the
|
|
300
|
-
stylesheet tells the two apart. `menus.primary` is printed inside the
|
|
301
|
-
`.global-header` on both: at the root it is a line of its own under the
|
|
302
|
-
tagline, and everywhere else the stylesheet lays the header out as one line so
|
|
303
|
-
it sits beside the link home. See [Navigation](#navigation).
|
|
304
|
-
|
|
305
|
-
**The footer** prints the copyright with the current year and the site
|
|
306
|
-
author's display name, `soloAuthor.name`, or the site title on a site with
|
|
307
|
-
several authors,
|
|
308
|
-
then the page's license, when it has one, as `<a rel="license">` with its name,
|
|
309
|
-
then `Published with Geekity`, and then `menus.footer` — the `footer` area this
|
|
310
|
-
theme declares, and the whole of what the footer links. A site that wants its
|
|
311
|
-
feed there types an `RSS | /feed/` line into it, the way the starter site does.
|
|
312
|
-
Nothing in the footer is read off an account: it used to hold one `rel="me"`
|
|
313
|
-
link per entry of `siteAuthor.links`, which was one nominated user's profile
|
|
314
|
-
presented as the site's. An empty or missing footer menu prints no list at all,
|
|
315
|
-
and the copyright line stands on its own. The year is
|
|
316
|
-
`{{ "now" | date("year") }}` — `now` is the one word the `date` filter reads
|
|
317
|
-
rather than parses — so it is the year at the moment the page is rendered, in
|
|
318
|
-
the site's own timezone. The license is `license`: a post's own from its
|
|
319
|
-
`license` front matter, else the site's from Settings > General, and nothing
|
|
320
|
-
on a site with none.
|
|
321
|
-
|
|
322
|
-
**The breadcrumb** opens `<main>` on a page with a place in the site's
|
|
323
|
-
hierarchy (TASK-150): a post filed under a category, and a category, tag or
|
|
324
|
-
author archive. It is `nav.breadcrumbs` labelled `Breadcrumb`, holding an `ol`
|
|
325
|
-
whose items run Home, then the post's first category and the post, or the
|
|
326
|
-
archive's term or person, then `Page N` past page one. Every crumb but the last
|
|
327
|
-
is a link; the last is the page itself, a `span` with `aria-current="page"`.
|
|
328
|
-
The trail is `breadcrumbs`, a list of `{ name, url }` that `layouts/base.njk`
|
|
329
|
-
sets before anything is printed, and the JSON-LD prints the same list, so the
|
|
330
|
-
two cannot disagree. Every other page has an empty trail and prints nothing. A
|
|
331
|
-
site that wants no visible breadcrumb empties the `breadcrumbs` block, and the
|
|
332
|
-
`BreadcrumbList` stays in the graph.
|
|
333
|
-
|
|
334
|
-
Webrings, badges and anything else that is markup rather than a link are
|
|
335
|
-
deliberately not in the package. They go in a site theme's `footer`
|
|
336
|
-
block:
|
|
337
|
-
|
|
338
|
-
```njk
|
|
339
|
-
{% extends "layouts/base.njk" %}
|
|
340
|
-
|
|
341
|
-
{% block footer %}
|
|
342
|
-
{{ super() }}
|
|
343
|
-
<p class="webring">
|
|
344
|
-
<a href="https://example.ring/previous">←</a>
|
|
345
|
-
An <a href="https://example.ring">example webring</a>
|
|
346
|
-
<a href="https://example.ring/next">→</a>
|
|
347
|
-
</p>
|
|
348
|
-
{% endblock %}
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
### The admin bar
|
|
352
|
-
|
|
353
|
-
When somebody signed in to the admin reads the site, the CMS puts the admin bar
|
|
354
|
-
across the top of every HTML page: the site title linking to the dashboard,
|
|
355
|
-
**+ New**, **Edit Post** or **Edit Page** on a post or a page, and the account
|
|
356
|
-
menu. A theme draws none of it and needs no template for it. The CMS inserts a
|
|
357
|
-
`<geekity-admin-bar>` element straight after the page's `<body>` tag, and an
|
|
358
|
-
anonymous reader's page is unchanged, byte for byte.
|
|
359
|
-
|
|
360
|
-
The bar keeps its markup and its stylesheet in a shadow root, so a theme's CSS
|
|
361
|
-
cannot restyle it and its CSS cannot restyle the theme. The element carries an
|
|
362
|
-
inline style that resets it and fixes it to the top of the window, full width,
|
|
363
|
-
above the page. Padding or margins on `<body>`, and rules such as `body > *` or
|
|
364
|
-
a grid on `<body>`, do not move or resize it. It stays in place while the page
|
|
365
|
-
scrolls.
|
|
366
|
-
|
|
367
|
-
The bar takes no room by itself, so the CMS makes room for it from outside. On
|
|
368
|
-
a page that carries the bar:
|
|
369
|
-
|
|
370
|
-
- `<html>` has the class `geekity-admin-bar`.
|
|
371
|
-
- A stylesheet in `<head>` declares `--geekity-admin-bar-height` on `:root`
|
|
372
|
-
and sets `margin-top: var(--geekity-admin-bar-height) !important` on
|
|
373
|
-
`<html>`, which pushes the whole page down below the bar. The height is
|
|
374
|
-
40px, or 80px on a screen 600px wide or less, where the bar can wrap onto a
|
|
375
|
-
second line. A script in the bar then measures it and sets the real height
|
|
376
|
-
on `<html>`, so the push matches the bar at any width, and is 0 when a theme
|
|
377
|
-
hides the bar.
|
|
378
|
-
- The same stylesheet sets `scroll-padding-top` on `<html>` to the bar's
|
|
379
|
-
height, so a link to an anchor on the page, such as a skip link or a
|
|
380
|
-
footnote, scrolls its target to below the bar. It is declared with no
|
|
381
|
-
specificity, so a theme that sets its own `scroll-padding-top` keeps it.
|
|
382
|
-
|
|
383
|
-
A theme with a sticky or fixed header of its own offsets it by the bar's
|
|
384
|
-
height. The fallback of `0` applies on every page the bar is not on:
|
|
385
|
-
|
|
386
|
-
```css
|
|
387
|
-
.site-header {
|
|
388
|
-
position: sticky;
|
|
389
|
-
top: var(--geekity-admin-bar-height, 0);
|
|
390
|
-
}
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
A theme that sets its own `scroll-padding-top` adds the bar to it the same
|
|
394
|
-
way, for example `calc(var(--geekity-admin-bar-height, 0px) + 4rem)`. Use the
|
|
395
|
-
`geekity-admin-bar` class for any other change, for example
|
|
396
|
-
`.geekity-admin-bar .site-header { ... }`.
|
|
397
|
-
|
|
398
|
-
A theme that must not show the bar can hide it:
|
|
399
|
-
|
|
400
|
-
```css
|
|
401
|
-
geekity-admin-bar {
|
|
402
|
-
display: none !important;
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
The `!important` is needed because it has to beat the element's inline style.
|
|
407
|
-
With script on, the bar then measures 0 and the page moves back up. For a
|
|
408
|
-
reader without script, the theme also removes the margin:
|
|
409
|
-
|
|
410
|
-
```css
|
|
411
|
-
html.geekity-admin-bar {
|
|
412
|
-
margin-top: 0 !important;
|
|
413
|
-
}
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
### An entry
|
|
417
|
-
|
|
418
|
-
`layouts/post.njk` and `layouts/page.njk` draw the same thing, the entry, and it
|
|
419
|
-
is a microformats2 `h-entry`. An article, and a page:
|
|
420
|
-
|
|
421
|
-
```html
|
|
422
|
-
<article class="blog-post h-entry">
|
|
423
|
-
<header class="post-header">
|
|
424
|
-
<p class="kicker">
|
|
425
|
-
<span class="kicker-kind">Article</span>
|
|
426
|
-
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
427
|
-
<a href="/category/notes/" class="p-category" rel="category">notes</a>
|
|
428
|
-
</p>
|
|
429
|
-
<h1 class="p-name">Hello</h1>
|
|
430
|
-
<p class="post-deck p-summary">The description the author wrote.</p>
|
|
431
|
-
</header>
|
|
432
|
-
<section class="e-content">
|
|
433
|
-
<p>The rendered body.</p>
|
|
434
|
-
<p class="entry-meta">
|
|
435
|
-
<a class="u-syndication small" href="https://news.indieweb.org/en"
|
|
436
|
-
>IndieNews</a
|
|
437
|
-
>
|
|
438
|
-
<a href="/2026/09/hello/" class="u-url"
|
|
439
|
-
><time class="small dt-published" datetime="2026-09-02T09:00:00.000Z"
|
|
440
|
-
>Published 2 September 2026</time
|
|
441
|
-
></a
|
|
442
|
-
>
|
|
443
|
-
<span aria-hidden="true">·</span>
|
|
444
|
-
<time class="small dt-updated" datetime="2026-09-05T09:00:00.000Z"
|
|
445
|
-
>Updated 5 September 2026</time
|
|
446
|
-
>
|
|
447
|
-
<span aria-hidden="true">·</span>
|
|
448
|
-
<span class="small"
|
|
449
|
-
>Also on
|
|
450
|
-
<a class="u-syndication" href="https://news.indieweb.org/en/…"
|
|
451
|
-
>news.indieweb.org</a
|
|
452
|
-
></span
|
|
453
|
-
>
|
|
454
|
-
</p>
|
|
455
|
-
</section>
|
|
456
|
-
<p class="post-categories">…tags…</p>
|
|
457
|
-
<hr />
|
|
458
|
-
<footer>…the bio…</footer>
|
|
459
|
-
</article>
|
|
460
|
-
<nav class="blog-post-nav" aria-label="Neighbouring posts">…</nav>
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
**The kicker names the kind.** `p.kicker`, from `partials/kicker.njk`, opens
|
|
464
|
-
every entry: the kind word in `span.kicker-kind` (Article, Note or Reply for a
|
|
465
|
-
post, from its `postType`; Page for a page), then the categories it is filed
|
|
466
|
-
under as `p-category` links. It is the only place the kind is said, small and
|
|
467
|
-
once, and an article is additionally the only kind with a visible headline.
|
|
468
|
-
The deck is the front matter's `description` as a `p.post-deck.p-summary`,
|
|
469
|
-
printed only when the author wrote one.
|
|
470
|
-
|
|
471
|
-
**The Published line is inside the `e-content`**, which is the one thing about
|
|
472
|
-
this shape worth knowing. It is what the source theme does: the date and the
|
|
473
|
-
permalink are part of the words, so a reader, or a fediverse peer reading the
|
|
474
|
-
`e-content`, takes them with the post. The permalink wraps the `dt-published`
|
|
475
|
-
time as a `u-url`, and a `dt-updated` follows it after a middle dot only when
|
|
476
|
-
the update happened on a different day in the site's own timezone; a typo fixed
|
|
477
|
-
an hour later is not news. The line opens with a `u-syndication` link to each
|
|
478
|
-
syndication target the post selects (`syndicateTo`, TASK-155), which is what
|
|
479
|
-
IndieNews and Bridgy Publish look for on the page, and ends with the post's
|
|
480
|
-
copies elsewhere (`syndication`) after **Also on**. A page never prints either,
|
|
481
|
-
and its line is a `p.page-meta` rather than a `p.entry-meta`.
|
|
482
|
-
|
|
483
|
-
**A post first published elsewhere says so.** A post whose front matter names
|
|
484
|
-
its original with `canonical_href`, an absolute http or https URL such as a
|
|
485
|
-
Substack essay's, has it on the context as `original` (TASK-293). The line
|
|
486
|
-
then reads **Originally published at** and links the original as a second
|
|
487
|
-
`u-url` of the h-entry, after the permalink's. The link says the publication's
|
|
488
|
-
name when the front matter gives one in `canonical_name`, such as `Smarter
|
|
489
|
-
Engineers` for a guest post on someone else's Substack (TASK-316), and the
|
|
490
|
-
original's host otherwise. It is no
|
|
491
|
-
`u-syndication`, because the original is not a copy of this post, and
|
|
492
|
-
original-post-discovery reads a copy's off-site `u-url` as its original. The
|
|
493
|
-
same URL is the page's `rel="canonical"` and its `og:url`, so search engines
|
|
494
|
-
credit the original. A value that is not an absolute http or https URL is
|
|
495
|
-
ignored, and `geekity sync` names the file. So is a `canonical_name` with no
|
|
496
|
-
valid `canonical_href` beside it.
|
|
497
|
-
|
|
498
|
-
**Every post's page has one `h1`.** A post with a name of its own is headed by
|
|
499
|
-
it, as above. So is a post with a title and no words, such as a titled like,
|
|
500
|
-
repost or bookmark (TASK-256): its header comes first and its citation after
|
|
501
|
-
it. A title that only repeats a post's opening words is no name. A note, or a
|
|
502
|
-
reply without a title, opens on its words instead,
|
|
503
|
-
set larger, so it has no `header`; in its place is an `h1` a screen reader and
|
|
504
|
-
a crawler navigate by and a sighted reader never sees, saying what the post is,
|
|
505
|
-
who wrote it and when, and then the kicker:
|
|
506
|
-
|
|
507
|
-
```html
|
|
508
|
-
<h1 class="screen-reader-text">Note by Ada Lovelace, 11 September 2026</h1>
|
|
509
|
-
<p class="kicker"><span class="kicker-kind">Note</span></p>
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
It says `Reply`, `Like`, `Repost` or `Bookmark` for those and leaves out the name or the date the post does
|
|
513
|
-
not have. It is not a `p-name`: a parser that found one would take the post
|
|
514
|
-
for an article. The headings under it step down one level at a time: the
|
|
515
|
-
conversation and the comment form are `h2`, and a refused form's error summary
|
|
516
|
-
is an `h3` inside the form's section.
|
|
517
|
-
|
|
518
|
-
**A reply cites what it answers** with `partials/reply-context.njk`, a
|
|
519
|
-
`div.reply-context.cite.u-in-reply-to.h-cite`: a rule on the start side, a
|
|
520
|
-
`p.cite-line` saying "In reply to" and the target's name, author and date as
|
|
521
|
-
far as they are known, and its excerpt as a `blockquote.cite-quote.p-content`.
|
|
522
|
-
Where it goes depends on whether the post shows a title. An untitled reply
|
|
523
|
-
opens on it, under the kicker and before its words. A titled reply prints its
|
|
524
|
-
`header.post-header` first and the citation after it, before the `e-content`,
|
|
525
|
-
so the page opens on its title rather than on a link. A listing follows the
|
|
526
|
-
same rule: an untitled entry cites under its kicker, a titled one after its
|
|
527
|
-
title and summary. The same partial cites a reply in a feed. The layouts own
|
|
528
|
-
the placement; the partial owns only what the citation says, and it stays
|
|
529
|
-
inside the `h-entry` wherever it is printed.
|
|
530
|
-
|
|
531
|
-
**A reply to a silo copy cites the original** (TASK-197). When the post a
|
|
532
|
-
reply answers is a copy of a post on another site, and that post lists the
|
|
533
|
-
copy as its `u-syndication`, `replyContext.original` is the original's URL and
|
|
534
|
-
the rest of `replyContext` describes the original. The `h-cite` links the
|
|
535
|
-
original, and a `p.cite-copy` after it says "Also in reply to its copy on" the
|
|
536
|
-
silo's host, linking the copy as a `u-in-reply-to` of its own, so a parser
|
|
537
|
-
reads both as what the post answers.
|
|
538
|
-
|
|
539
|
-
**An RSVP says whether its author is going** (TASK-198). An RSVP is a reply
|
|
540
|
-
to an event whose `rsvp` is `yes`, `no`, `maybe` or `interested`.
|
|
541
|
-
`partials/rsvp.njk` opens its `e-content` with a `p.rsvp-line` holding a
|
|
542
|
-
`data.p-rsvp` whose value is the answer and whose text is Going, Not going,
|
|
543
|
-
Maybe or Interested; it prints `rsvp.line`, the HTML the feeds also carry. Its
|
|
544
|
-
reply context says "RSVP to" rather than "In reply to", and when the event's
|
|
545
|
-
page is an `h-event`, or describes a schema.org `Event` in JSON-LD, its start
|
|
546
|
-
follows the event's name as a `time.dt-start` and its place as a
|
|
547
|
-
`span.p-location`, inside the `h-cite`. A start the event page wrote with no
|
|
548
|
-
zone is that wall clock where the event is, so it is printed in UTC
|
|
549
|
-
(`replyContext.startZone`) rather than moved into the site's zone. Its kicker
|
|
550
|
-
and its hidden `h1` say RSVP. An RSVP to a silo copy of an event cites the
|
|
551
|
-
original as a reply does.
|
|
552
|
-
|
|
553
|
-
**An event is an h-event** (TASK-200, decision-32). A post whose front matter
|
|
554
|
-
has a readable `start` is an event: its title is the event's name, its body
|
|
555
|
-
the description, and `end` and `location` are optional. `layouts/post.njk`
|
|
556
|
-
types its article `h-event` rather than `h-entry`, its kicker and hidden `h1`
|
|
557
|
-
say Event, and `partials/event.njk` follows the title with a
|
|
558
|
-
`dl.event-details`. The When row is a `time.dt-start` and, with an end, a
|
|
559
|
-
`time.dt-end`, each written with the `date` filter's `datetime` format in the
|
|
560
|
-
site's zone, with the instant as its `datetime`. The Where row is a
|
|
561
|
-
`span.p-location` holding the place's words, or "Online at" an
|
|
562
|
-
`a.p-location` that links the address to join an online event. The JSON-LD
|
|
563
|
-
describes the post as a schema.org `Event` instead of a `BlogPosting`. That
|
|
564
|
-
`Event` is a `Place` or a `VirtualLocation`, carries an attendance mode and an
|
|
565
|
-
`EventScheduled` status, and names the author's Person as its `organizer`.
|
|
566
|
-
The answers it is given are grouped in `conversation.rsvps`; see [The
|
|
567
|
-
conversation](#the-conversation).
|
|
568
|
-
|
|
569
|
-
**A like, a repost or a bookmark cites what it cites** (TASK-169) with
|
|
570
|
-
`partials/citations.njk`, beside the reply context and drawn the same way: one
|
|
571
|
-
`div.reply-context.cite.h-cite` per entry of `citations`, classed
|
|
572
|
-
`u-repost-of`, `u-like-of` or `u-bookmark-of`, whose `p.cite-line` says
|
|
573
|
-
Reposted, Liked or Bookmarked and links the target as its `u-url`. The link
|
|
574
|
-
says the target's name, as the reply context does, with its author after it as
|
|
575
|
-
a `p-author h-card`, when the target was read when the post was saved
|
|
576
|
-
(TASK-244): its `h-entry` name, its oEmbed title, or its `og:title` or
|
|
577
|
-
`<title>`. A YouTube, TikTok, Reddit or Giphy target is named by that
|
|
578
|
-
provider's known oEmbed endpoint, asked before its page, which is read only
|
|
579
|
-
when the endpoint names nothing (TASK-251, TASK-253). Any other target's oEmbed
|
|
580
|
-
title comes from the endpoint its page links. A title that is only a site suffix such as "- YouTube" is
|
|
581
|
-
no title. A comment on this site (`replyContext.comment`, TASK-326) says "a
|
|
582
|
-
comment". With only an author it says "a post", and with neither "a page on"
|
|
583
|
-
the target's host, never the bare URL, which can run to hundreds of
|
|
584
|
-
characters: "Bookmarked a page on example.com" (TASK-255). Its kicker
|
|
585
|
-
and its hidden `h1` say Repost, Like or Bookmark. It is placed as the reply
|
|
586
|
-
context is: at the top of an untitled post, after the header of a titled one.
|
|
587
|
-
A listing cites each entry's from the same partial, by the same rule.
|
|
588
|
-
|
|
589
|
-
**Both citations name the cited page** through `partials/cited-page.njk`, so
|
|
590
|
-
they print its name and author by one rule. A name that already ends in " by "
|
|
591
|
-
and the author's name, in any case, does not print the author again
|
|
592
|
-
(TASK-247): SoundCloud titles a track "Flickermood by Forss" and names Forss
|
|
593
|
-
as its author. The title link stops before " by ", and the words after it
|
|
594
|
-
become the `p-author h-card`, so the line reads "Liked Flickermood by Forss"
|
|
595
|
-
and a parser still reads the whole title as the `p-name`. A name that mentions
|
|
596
|
-
its author anywhere else, such as "Rick Astley - Never Gonna Give You Up" by
|
|
597
|
-
Rick Astley, keeps the author after it. A page that names no author but names
|
|
598
|
-
its site, its `og:site_name`, is followed by a middle dot and the site as a
|
|
599
|
-
plain `span.cite-site`, never an `h-card`, because a site is not an author: a
|
|
600
|
-
bookmark of a Scripting News post reads "Bookmarked RSS tip #2 · Scripting
|
|
601
|
-
News". A like's, a repost's or a bookmark's citation never prints the page's
|
|
602
|
-
description, which on many sites describes the site rather than the post.
|
|
603
|
-
|
|
604
|
-
**A citation shows who wrote the cited page** (TASK-199) as fully as the page
|
|
605
|
-
said. The author's photo, an `img.u-photo.cite-avatar` with empty alt text
|
|
606
|
-
since their name is beside it, comes before the name inside the `h-card`; a
|
|
607
|
-
fediverse author's handle follows the name as a `span.p-nickname.cite-handle`
|
|
608
|
-
reading `@user@host`. The photo is the `u-photo` of the `h-entry` author's
|
|
609
|
-
`h-card` (or of the page's `h-card` for the same person), the avatar of the
|
|
610
|
-
account an ActivityPub object is attributed to, or the author image in the
|
|
611
|
-
page's JSON-LD, copied into the site's uploads as a picture is, and left out
|
|
612
|
-
when the post has removed its preview. A Mastodon status is read from the
|
|
613
|
-
ActivityPub object its page links, so its author's display name, handle,
|
|
614
|
-
avatar, words, first image and date come from the status itself rather than
|
|
615
|
-
from the page's `og:title`. A page with no `h-entry` and no object is read
|
|
616
|
-
from its JSON-LD (`headline`, `author`, `datePublished`, `image`) and then its
|
|
617
|
-
Open Graph, Twitter and `article:` tags.
|
|
618
|
-
|
|
619
|
-
**A citation shows the cited page's picture** (TASK-252) when the page named
|
|
620
|
-
one and the post has not removed its preview. The picture is the page's
|
|
621
|
-
oEmbed photo, else its oEmbed thumbnail, else its `og:image`, else its
|
|
622
|
-
`twitter:image`, copied into the site's uploads when the post was saved, so
|
|
623
|
-
a reader's browser never asks the cited site or its CDN for anything. It is
|
|
624
|
-
`context.picture`: `{ src, width, height, kind, video, shownInFull, imgHtml }`, where
|
|
625
|
-
`imgHtml` is an `img.u-photo` inside the `h-cite`, responsive when the copy has
|
|
626
|
-
variants. A repost of a photo (`shownInFull`, such as a Giphy GIF) prints it in full
|
|
627
|
-
under the line, as `a.cite-photo` linking to the target, with the target's
|
|
628
|
-
name as its alt text. Every other citation with a picture (a like, a bookmark,
|
|
629
|
-
a reply, a repost of a video or a page) is classed `cite-with-thumb` and
|
|
630
|
-
prints it as `a.cite-thumb` beside the line: a 6rem thumbnail linking to the
|
|
631
|
-
target, out of the tab order and hidden from assistive technology because the
|
|
632
|
-
title beside it is the same link, with an empty alt text. A video's thumbnail
|
|
633
|
-
is also classed `cite-video`, which the stylesheet draws a play mark on. A
|
|
634
|
-
picture that could not be copied, was bigger than the site's upload limit or
|
|
635
|
-
was never named leaves the citation as it was, with no picture. A post whose
|
|
636
|
-
front matter says `preview: false`, which the editor's remove control writes,
|
|
637
|
-
prints the plain citation, and so do its listing entry and its feed items.
|
|
638
|
-
There is never an iframe or a script from the cited page.
|
|
639
|
-
|
|
640
|
-
**A cited URL that is itself an image** (TASK-255), one that answers with an
|
|
641
|
-
`image/*` type rather than a page, is copied as its own picture of kind
|
|
642
|
-
`photo` and has no name, so `context.image` is true and the line says "an
|
|
643
|
-
image from" its host as the link: "Reposted an image from edu.casio.com". A
|
|
644
|
-
repost shows it in full like a reposted GIF, a like or a bookmark as a
|
|
645
|
-
thumbnail beside "Liked an image from edu.casio.com", and a post that hid its
|
|
646
|
-
preview still says it reposted an image. Its alt text is the post's
|
|
647
|
-
`cited-alt`, which the editor's card writes, else the post's title, else
|
|
648
|
-
empty. An image that could not be copied, or was bigger than the site's
|
|
649
|
-
upload limit, keeps no context, so its citation says "a page on" its host.
|
|
650
|
-
|
|
651
|
-
The feeds open each
|
|
652
|
-
citing post's HTML with the same citation as a `p.cite-line`, the picture
|
|
653
|
-
after it with an absolute `src`.
|
|
654
|
-
|
|
655
|
-
**A read says what was read** (TASK-229) with `partials/read.njk`, a
|
|
656
|
-
`p.read-line` at the top of the `e-content` that reads like indiebookclub's
|
|
657
|
-
own: a `data.p-read-status` whose value is `to-read`, `reading` or `finished`
|
|
658
|
-
and whose text is Want to read, Currently reading or Finished reading, then a
|
|
659
|
-
`span.p-read-of.h-cite` with the title as a `cite.p-name` (linked as its
|
|
660
|
-
`u-url` when the post gives one), the author as a `p-author`, and the
|
|
661
|
-
identifier as a `data.p-uid` whose value is the `isbn:` or `doi:` uid. Its
|
|
662
|
-
kicker and its hidden `h1` say Read. A listing prints each entry's from the
|
|
663
|
-
same partial. The partial prints `read.line`, the same HTML the feeds and the
|
|
664
|
-
federated Note open with (TASK-233); `read.status`, `read.statusLabel` and
|
|
665
|
-
`read.of` are there for a theme that marks it up differently.
|
|
666
|
-
|
|
667
|
-
The tags are printed under the words and inside the article, so that each link
|
|
668
|
-
is a `p-category` of this entry, as one `p.post-categories` from
|
|
669
|
-
`partials/tags.njk`. The stylesheet draws the `#` in front of each, so the
|
|
670
|
-
category a parser reads is the tag and not the hash.
|
|
671
|
-
|
|
672
|
-
**A post with a recording plays it** (TASK-213), from
|
|
673
|
-
`partials/recording.njk`, between the header and the `e-content`. The main file
|
|
674
|
-
is the browser's own player, `<audio controls>` or `<video controls>` as
|
|
675
|
-
`enclosure.player` says, with `preload="metadata"` so a reader who never presses
|
|
676
|
-
play downloads nothing, and a download link inside it for a browser that plays
|
|
677
|
-
neither. The `<source>` carries `u-audio` or `u-video`, so a microformats parser
|
|
678
|
-
finds the file on the entry. A video whose transcript is WebVTT also gets it as
|
|
679
|
-
a `<track kind="captions">`. Under the player a `figcaption` links the
|
|
680
|
-
transcript, as Captions for VTT or SRT and Transcript otherwise, and lists the
|
|
681
|
-
other versions by their title, else their media type. A post without a
|
|
682
|
-
recording, or whose front matter is missing what a player needs, prints none of
|
|
683
|
-
it.
|
|
684
|
-
|
|
685
|
-
**A post with photos shows them** (TASK-166), from `partials/photos.njk`, after
|
|
686
|
-
the recording and before the `e-content`, each in a `figure.post-photo`. Each is
|
|
687
|
-
the context's `photo.html`: an `<img class="u-photo">` with its alt text, so a
|
|
688
|
-
microformats parser finds every photo on the entry, and a `<picture>` with the
|
|
689
|
-
upload's resizes when it has them. The alt text is the post's own, else the
|
|
690
|
-
media library's. A listing prints them the same way above each entry, and the
|
|
691
|
-
JSON-LD `BlogPosting` lists each as an `ImageObject` with its absolute `url`
|
|
692
|
-
and its alt text as `caption`. The first photo is the post's share image when
|
|
693
|
-
its front matter names no `image`.
|
|
694
|
-
|
|
695
|
-
```html
|
|
696
|
-
<figure class="post-recording">
|
|
697
|
-
<audio controls preload="metadata" aria-label="Recording of Episode twelve">
|
|
698
|
-
<source
|
|
699
|
-
class="u-audio"
|
|
700
|
-
src="/uploads/2026/10/episode-12.mp3"
|
|
701
|
-
type="audio/mpeg"
|
|
702
|
-
/>
|
|
703
|
-
<a href="/uploads/2026/10/episode-12.mp3">Download the recording</a>
|
|
704
|
-
</audio>
|
|
705
|
-
<figcaption class="post-recording-links">
|
|
706
|
-
<p><a href="/uploads/2026/10/episode-12.vtt">Captions</a></p>
|
|
707
|
-
<p id="post-recording-versions">Other versions</p>
|
|
708
|
-
<ul aria-labelledby="post-recording-versions">
|
|
709
|
-
<li>
|
|
710
|
-
<a href="/uploads/2026/10/episode-12.mp4" type="video/mp4">Video</a>
|
|
711
|
-
</li>
|
|
712
|
-
</ul>
|
|
713
|
-
</figcaption>
|
|
714
|
-
</figure>
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
After the entry a post prints its backlinks, then `nav.blog-post-nav`: the
|
|
718
|
-
`previous` and `next` posts as two cards, `rel="prev"` and `rel="next"`, each
|
|
719
|
-
opening on a `span.blog-post-nav-label`, and nothing at all at the ends of the
|
|
720
|
-
archive; then the conversation and the comment form. A page prints its
|
|
721
|
-
backlinks too, then the conversation and the comment form when it takes
|
|
722
|
-
comments, and the contact form when its front matter asked for one. A page has
|
|
723
|
-
no neighbours, no tags and no syndication links, because none of those are
|
|
724
|
-
things a page has.
|
|
725
|
-
|
|
726
|
-
### Backlinks
|
|
727
|
-
|
|
728
|
-
A link from one of the site's posts or pages to another sends no webmention.
|
|
729
|
-
The CMS records it instead, and the linked document's context carries
|
|
730
|
-
`backlinks`: the listed posts and pages whose body links to it, newest first,
|
|
731
|
-
each as `{ title, url, date }`. `title` is the linking document's title, or a
|
|
732
|
-
note's first words. `date` is absent for a page that has none. A link counts
|
|
733
|
-
whether it is absolute or relative, with or without its trailing slash, a query
|
|
734
|
-
or a fragment, and through a `redirect_from` of the target. A link to `/`
|
|
735
|
-
counts for the page the Reading setting makes the homepage. A link to a URL the
|
|
736
|
-
site answers with a redirect counts for the document the redirect leads to,
|
|
737
|
-
whether the redirect is a `redirect_from` or an entry in `_data/redirects.json`
|
|
738
|
-
or `_data/redirects/*.json`, `/?p=123` included. A link with a query names the
|
|
739
|
-
query's redirect only when the query matches the redirect's source exactly, as
|
|
740
|
-
a request does, so `/?p=123&ref=feed` is a link to `/`. A document that links
|
|
741
|
-
to itself is not its own backlink. Drafts, future-dated, unlisted,
|
|
742
|
-
private and trashed documents never appear. `backlinks` is always a list, and
|
|
743
|
-
it is empty when nothing links to the document.
|
|
744
|
-
|
|
745
|
-
`partials/backlinks.njk` prints a `section.backlinks` under the entry, apart
|
|
746
|
-
from the conversation: an `h2` that says "Linked from", then an `ol` of links,
|
|
747
|
-
each with its date in a `<time>`. It prints nothing for an empty list, so
|
|
748
|
-
`layouts/post.njk` and `layouts/page.njk` include it with no condition. The
|
|
749
|
-
front page has `backlinks` on its context too, but `layouts/front-page.njk`
|
|
750
|
-
does not print them, because most of a site links home. A site theme that wants
|
|
751
|
-
them there adds the same include to its own `layouts/front-page.njk`:
|
|
752
|
-
|
|
753
|
-
```njk
|
|
754
|
-
{% include "partials/backlinks.njk" %}
|
|
755
|
-
```
|
|
756
|
-
|
|
757
|
-
A site theme moves the list by including the partial somewhere else in its own
|
|
758
|
-
layout, and leaves it out by overriding a layout without the include. To change
|
|
759
|
-
the markup, add a `partials/backlinks.njk` to the site theme. That file replaces
|
|
760
|
-
this one, as any other template is replaced.
|
|
761
|
-
|
|
762
|
-
### The bio
|
|
763
|
-
|
|
764
|
-
`partials/bio.njk` is who the page is by: the `p-author h-card` the rule and
|
|
765
|
-
the footer at the end of an entry hold, and the heading of an author archive.
|
|
766
|
-
It replaces `partials/byline.njk`, which is gone — a byline was a name in a
|
|
767
|
-
meta line, and this is the whole credit.
|
|
768
|
-
|
|
769
|
-
```njk
|
|
770
|
-
{% set bioAuthor = author or siteAuthor %}
|
|
771
|
-
{% include "partials/bio.njk" %}
|
|
772
|
-
```
|
|
773
|
-
|
|
774
|
-
`bioAuthor` is the one thing to set, and nothing renders when it is absent.
|
|
775
|
-
It is one of the profile objects the context already carries: `author` on a
|
|
776
|
-
post, falling back to `siteAuthor`; `siteAuthor` on a page; the archive's
|
|
777
|
-
person on an author archive.
|
|
778
|
-
|
|
779
|
-
What the bio prints: a round `u-photo` at 50px when they have an avatar,
|
|
780
|
-
"Written by" and their name as a `p-name u-url` linked `rel="author me"` to
|
|
781
|
-
their archive, then `p-job-title` and `p-locality` when the profile says, then
|
|
782
|
-
their `p-note` and their `rel="me"` links as a `ul.hlist.bio-links` — each
|
|
783
|
-
link's `rel` is on it ready to print, `me` first and then whatever else the
|
|
784
|
-
Links box typed. Set `bioRelMe` to `false` to keep the card and drop its
|
|
785
|
-
claims: the name is linked `rel="author"` alone and `me` comes off every
|
|
786
|
-
link.
|
|
787
|
-
|
|
788
|
-
**The homepage** speaks for the site's author only on a site whose Site author
|
|
789
|
-
setting names a user, which `soloAuthor` on the context says. Then
|
|
790
|
-
`layouts/base.njk` puts that person's `rel="me"` claims in the head of `/` as
|
|
791
|
-
`<link rel="me">` elements, one for their author archive and one for each
|
|
792
|
-
profile link, whichever layout draws the page (decision-45). Their author
|
|
793
|
-
archive's link home carries `rel="me"` back, so the homepage and the archive
|
|
794
|
-
name each other. `layouts/home.njk` at `/` also ends with that person's card,
|
|
795
|
-
with the same claims, and sets `bioHome` to the homepage's absolute URL, which
|
|
796
|
-
puts a `data.u-url.u-uid` holding it first in the card, so a parser running the
|
|
797
|
-
[representative h-card algorithm](https://microformats.org/wiki/representative-h-card-parsing)
|
|
798
|
-
on the homepage finds this card as the site's (TASK-193). The archive stays a
|
|
799
|
-
second `u-url`. No other page sets `bioHome`. A static front page prints no
|
|
800
|
-
card: see [The front page](#the-front-page) for how a site puts one back. On a
|
|
801
|
-
site with several authors neither homepage prints a card or makes a claim. Each of
|
|
802
|
-
those is printed only when the profile says it, so a profile holding a name
|
|
803
|
-
alone prints a name alone. A name this site has no account for is printed
|
|
804
|
-
unlinked, because the file still said somebody wrote this.
|
|
805
|
-
|
|
806
|
-
**The person and only the person.** The site menu used to be printed here as
|
|
807
|
-
well, because the design had no header navigation; it is in the header on
|
|
808
|
-
every page now — see [Navigation](#navigation) — so the bio says nothing about
|
|
809
|
-
the site. The note and the links used to be behind a `bioProfile` switch that
|
|
810
|
-
an author archive set and an entry did not, on the grounds that the page
|
|
811
|
-
footer already carried the site's identity links. It carries nobody's, so the
|
|
812
|
-
switch is gone and the card is the same wherever it appears: at the top of
|
|
813
|
-
somebody's archive, and under each of their posts.
|
|
814
|
-
|
|
815
|
-
`bioLead` is what the line opens with, `Written by` unless a layout sets
|
|
816
|
-
another; `layouts/author.njk` sets `Posts by`, because the card there heads
|
|
817
|
-
somebody's writing rather than crediting one piece of it.
|
|
818
|
-
|
|
819
|
-
**A site that used `partials/byline.njk`** — `{{ byline.line(author) }}` from
|
|
820
|
-
an overridden layout — either includes this partial instead or writes the line
|
|
821
|
-
itself. It was four lines of markup, and the object behind it has not changed:
|
|
822
|
-
|
|
823
|
-
```njk
|
|
824
|
-
{% set writer = author or { name: site.title } %}
|
|
825
|
-
<span class="p-author h-card">
|
|
826
|
-
{%- if writer.url %}<a class="u-url" href="{{ writer.url | url }}">{{ writer.name }}</a>
|
|
827
|
-
{%- else %}{{ writer.name }}{% endif %}
|
|
828
|
-
</span>
|
|
829
|
-
```
|
|
830
|
-
|
|
831
|
-
`apps/demo/themes/demo/layouts/post.njk` is that, worked: the demo wants a
|
|
832
|
-
byline under the title rather than a bio in the footer, so it writes one.
|
|
833
|
-
|
|
834
|
-
### A listing
|
|
835
|
-
|
|
836
|
-
`partials/post-list.njk` is the feed every listing is made of (the home page,
|
|
837
|
-
the posts page, a tag, category or author archive, and a front page that
|
|
838
|
-
lists `newestPosts`) and it is a microformats2 `h-feed` of `h-entry` items, each opening on
|
|
839
|
-
its kicker:
|
|
840
|
-
|
|
841
|
-
```html
|
|
842
|
-
<div class="feed h-feed">
|
|
843
|
-
<article class="feed-item h-entry">
|
|
844
|
-
<p class="kicker">
|
|
845
|
-
<span class="kicker-kind">Article</span>
|
|
846
|
-
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
847
|
-
<time class="feed-date dt-published" datetime="…">2 September 2026</time>
|
|
848
|
-
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
849
|
-
<a href="/category/notes/" class="p-category" rel="category">notes</a>
|
|
850
|
-
</p>
|
|
851
|
-
<h2 class="feed-title p-name">
|
|
852
|
-
<a href="/2026/09/hello/" class="u-url">Hello</a>
|
|
853
|
-
</h2>
|
|
854
|
-
<div class="feed-excerpt p-summary">
|
|
855
|
-
<p>What the post is about, in about 280 characters...</p>
|
|
856
|
-
</div>
|
|
857
|
-
<p class="feed-more">
|
|
858
|
-
<a href="/2026/09/hello/" aria-label="Continue reading: Hello">
|
|
859
|
-
Continue reading<span aria-hidden="true"> →</span>
|
|
860
|
-
</a>
|
|
861
|
-
</p>
|
|
862
|
-
</article>
|
|
863
|
-
<hr class="feed-separator" />
|
|
864
|
-
<article class="feed-item h-entry">
|
|
865
|
-
<p class="kicker">
|
|
866
|
-
<span class="kicker-kind">Note</span>
|
|
867
|
-
<span class="kicker-dot" aria-hidden="true">·</span>
|
|
868
|
-
<a href="/2026/09/coffee/" class="u-url"
|
|
869
|
-
><time class="feed-date dt-published" datetime="…"
|
|
870
|
-
>3 September 2026</time
|
|
871
|
-
></a
|
|
872
|
-
>
|
|
873
|
-
</p>
|
|
874
|
-
<div class="feed-excerpt e-content"><p>The whole note.</p></div>
|
|
875
|
-
</article>
|
|
876
|
-
</div>
|
|
877
|
-
```
|
|
878
|
-
|
|
879
|
-
**Only an article has a headline.** A note, which has no title, is printed
|
|
880
|
-
whole as its `e-content`, and the date in its kicker is its `u-url`. A reply is
|
|
881
|
-
a note that first cites what it answers with `partials/reply-context.njk`, and
|
|
882
|
-
a reply with a title is an article that cites it after its title and summary.
|
|
883
|
-
A like, a repost and a bookmark place `partials/citations.njk` the same way,
|
|
884
|
-
and one with a title and no words is listed by its title too. An article's categories are in
|
|
885
|
-
its kicker; a note's are on its own page.
|
|
886
|
-
|
|
887
|
-
The excerpt is the entry's `summary`, the very line the feeds publish, so a
|
|
888
|
-
reader and a feed cannot be told two different things, through Nunjucks'
|
|
889
|
-
`truncate(280)`, which cuts at the last space before 280 characters and adds an
|
|
890
|
-
ellipsis. A `description` shorter than that is printed whole. It is plain text
|
|
891
|
-
rather than markup, deliberately: an excerpt with half a code block in it is
|
|
892
|
-
not an excerpt.
|
|
893
|
-
|
|
894
|
-
Each entry of a listing carries `replyContext` beside `inReplyTo` when it is a
|
|
895
|
-
reply whose target has been fetched, the same object a reply's own page gets,
|
|
896
|
-
so the feed can cite it in full.
|
|
897
|
-
|
|
898
|
-
An entry's tags are not in a feed item. They belong under the entry, where
|
|
899
|
-
there is one post's worth of them rather than twenty.
|
|
900
|
-
|
|
901
|
-
**The heading level belongs to the layout**, not to the list. `feedHeading` is
|
|
902
|
-
the level `post-list.njk` prints its titles at and defaults to 2, which is what
|
|
903
|
-
a listing wants; a page whose feed sits under a heading of its own sets 3
|
|
904
|
-
before including it, so the outline never skips a level:
|
|
905
|
-
|
|
906
|
-
```njk
|
|
907
|
-
{% set feedHeading = 3 %}
|
|
908
|
-
{% include "partials/post-list.njk" %}
|
|
909
|
-
```
|
|
910
|
-
|
|
911
|
-
A listing with nothing on it says `No posts found.` in `p.empty`.
|
|
912
|
-
|
|
913
|
-
`partials/pagination.njk` prints two links in `nav.pagination`, `← Previous`
|
|
914
|
-
to the page before this one and `Next →` to the page after it, with `rel="prev"`
|
|
915
|
-
and `rel="next"`, and nothing at all on a listing of one page. The arrows
|
|
916
|
-
themselves are `aria-hidden`, because a screen reader that announced them would
|
|
917
|
-
read the decoration and then the word.
|
|
918
|
-
|
|
919
|
-
Each listing layout heads its own page: `layouts/home.njk` with the listing's
|
|
920
|
-
title, and the three archives with a `header.archive-header` whose kicker says
|
|
921
|
-
what kind of archive it is (Tag, Category, Author) over an `h1.page-title`
|
|
922
|
-
naming whose: the term, or the person, followed by their `h-card`. The
|
|
923
|
-
category archive's header is also a `header.category-header` and prints a
|
|
924
|
-
`div.category-description` under the name when the context carries a
|
|
925
|
-
`categoryDescription`; the CMS has no store of term descriptions, so nothing
|
|
926
|
-
writes one today and the header is the heading alone.
|
|
927
|
-
|
|
928
|
-
`layouts/404.njk` says `Content not found.` under a `Not found` kicker and
|
|
929
|
-
links home and to the search. The 500 and 503 pages below open on a kicker the
|
|
930
|
-
same way.
|
|
931
|
-
|
|
932
|
-
`layouts/410.njk` is the page at the URL of a post or page that was moved to
|
|
933
|
-
the trash (TASK-195). It says `This content has been deleted.` under a
|
|
934
|
-
`Deleted` kicker and links home and to the search. The context is the 404's:
|
|
935
|
-
`title`, `url` and `page.url`, and nothing of the deleted document. Its `.md`
|
|
936
|
-
and `.json` representations answer 410 too. A trashed draft was never public,
|
|
937
|
-
so its URL is a 404 instead.
|
|
938
|
-
|
|
939
|
-
`layouts/500.njk` is the page a request gets when the server fails while
|
|
940
|
-
answering it (TASK-129). It says `Something went wrong.` in an
|
|
941
|
-
`article.server-error` and links home. The context is the 404's: `title`,
|
|
942
|
-
`url` and `page.url`. The response is a 500 with `Cache-Control: no-store`, and
|
|
943
|
-
the error itself goes to the server's log, never into the page. If this template
|
|
944
|
-
fails too, the CMS answers with a plain HTML page of its own instead, so a theme
|
|
945
|
-
cannot turn one failure into a crash. A request for the `.md` or `.json`
|
|
946
|
-
representation gets a 500 in that format and never reaches this template.
|
|
947
|
-
|
|
948
|
-
`layouts/503.njk` is the page a request gets while the site is in maintenance
|
|
949
|
-
mode (TASK-130). It says `Down for maintenance.` in an `article.maintenance`.
|
|
950
|
-
The context is the 404's, plus `until` (an ISO 8601 time) and `untilText` (the
|
|
951
|
-
same time as an HTTP date) when the operator named when the site should be
|
|
952
|
-
back. The response is a 503 with `Retry-After` and `Cache-Control: no-store`.
|
|
953
|
-
The theme's files under `/theme/` are still served, so the page can use the
|
|
954
|
-
stylesheet. If this template fails, the CMS answers with a plain HTML page of
|
|
955
|
-
its own.
|
|
956
|
-
|
|
957
|
-
### Search
|
|
958
|
-
|
|
959
|
-
`layouts/search.njk` is the page at `/search/` (TASK-22). It prints
|
|
960
|
-
`partials/search-form.njk`, and once there is a `query` a `p.search-summary`
|
|
961
|
-
saying how many documents matched, a `div.search-results` of
|
|
962
|
-
`article.search-result` entries, and the ordinary pager. It asks not to be
|
|
963
|
-
indexed with `<meta name="robots" content="noindex">` in its `head` block.
|
|
964
|
-
|
|
965
|
-
The context is a listing's with two differences. `query` is the words searched
|
|
966
|
-
for, trimmed, and an empty string on the page before a search. Each entry in
|
|
967
|
-
`posts` carries a `snippet`: a few words of
|
|
968
|
-
HTML around the match, escaped, with every matched word in `<mark>`. Print it
|
|
969
|
-
with `safe`. The results are best match first rather than newest first, and
|
|
970
|
-
only what the public site would serve is ever among them.
|
|
971
|
-
|
|
972
|
-
`partials/search-form.njk` is a `GET` form to `/search/` with the words in `q`,
|
|
973
|
-
so it needs no JavaScript and a search is a URL. This layout is the only thing
|
|
974
|
-
that includes it: the box used to be in the footer of every page as well, and
|
|
975
|
-
search is a menu item now — a `Search | /search/` line like any other. The
|
|
976
|
-
search page takes the path `/search/` ahead of any document permalinked there.
|
|
977
|
-
|
|
978
|
-
### The front page
|
|
979
|
-
|
|
980
|
-
`layouts/front-page.njk` is what a site gets at `/` once the Reading settings
|
|
981
|
-
name a homepage (`homepage` in `content/_data/site.json`). It is that page read
|
|
982
|
-
somewhere else: the same document, the same context, with `page.url` saying `/`
|
|
983
|
-
rather than the permalink that redirects there.
|
|
984
|
-
|
|
985
|
-
It draws the page's own title and words, and nothing else (decision-44):
|
|
986
|
-
|
|
987
|
-
1. `h2.section-title`, the page's title. It is an `h2` because
|
|
988
|
-
`layouts/base.njk` heads the root path with the site title as the `h1`.
|
|
989
|
-
2. `div.page-body.e-content`, the rendered body. There is no Published line,
|
|
990
|
-
because a front page is read as the site rather than as a page somebody
|
|
991
|
-
wrote on a Tuesday.
|
|
992
|
-
3. The contact form, when the page's front matter says `contact: true`.
|
|
993
|
-
|
|
994
|
-
It lists no posts, prints no line of links and prints no bio. A site that wants
|
|
995
|
-
any of them on its front page overrides `layouts/front-page.njk` in its own
|
|
996
|
-
theme. Two context keys are the front page's alone, for that override:
|
|
997
|
-
|
|
998
|
-
- `newestPosts(count)` is a function. It returns the newest `count` published
|
|
999
|
-
posts as entries, in the same shape a listing's `posts` are, so
|
|
1000
|
-
`partials/post-list.njk` prints them. Drafts, future-dated, unlisted and
|
|
1001
|
-
trashed posts are left out. The theme chooses the count. The query runs only
|
|
1002
|
-
when a template calls it, so a front page that lists nothing costs nothing.
|
|
1003
|
-
A count that is not a whole number of at least 1 is an error.
|
|
1004
|
-
- `postsPage` is `{ title, url }` for the page carrying the listing, and is
|
|
1005
|
-
**absent** when the site names none, because a link to a listing that has no
|
|
1006
|
-
URL is a link to nothing.
|
|
1007
|
-
|
|
1008
|
-
`newestPosts` replaces `recentPosts`, which held this month's posts when there
|
|
1009
|
-
were at least five and the five newest otherwise. An override that read
|
|
1010
|
-
`recentPosts` calls `newestPosts(5)` instead. This override draws the page's
|
|
1011
|
-
words, the five newest posts, a line of links and the site author's card as
|
|
1012
|
-
the site's representative h-card:
|
|
1013
|
-
|
|
1014
|
-
```njk
|
|
1015
|
-
{% extends "layouts/base.njk" %}
|
|
1016
|
-
|
|
1017
|
-
{% block content %}
|
|
1018
|
-
<div class="page-body e-content">
|
|
1019
|
-
{{ content | safe }}
|
|
1020
|
-
</div>
|
|
1021
|
-
|
|
1022
|
-
<h2 class="section-title">Recent Posts</h2>
|
|
1023
|
-
{% set posts = newestPosts(5) %}
|
|
1024
|
-
{% set feedHeading = 3 %}
|
|
1025
|
-
{% include "partials/post-list.njk" %}
|
|
1026
|
-
|
|
1027
|
-
<p class="front-links">
|
|
1028
|
-
{% if postsPage %}
|
|
1029
|
-
<a href="{{ postsPage.url | url }}">{{ postsPage.title }}<span aria-hidden="true"> →</span></a>
|
|
1030
|
-
{% endif %}
|
|
1031
|
-
<a href="{{ "/search/" | url }}">Search<span aria-hidden="true"> →</span></a>
|
|
1032
|
-
</p>
|
|
1033
|
-
|
|
1034
|
-
{% if soloAuthor %}
|
|
1035
|
-
{% set bioAuthor = soloAuthor %}
|
|
1036
|
-
{% set bioHome = "/" | absoluteUrl %}
|
|
1037
|
-
<hr>
|
|
1038
|
-
<footer>
|
|
1039
|
-
{% include "partials/bio.njk" %}
|
|
1040
|
-
</footer>
|
|
1041
|
-
{% endif %}
|
|
1042
|
-
{% endblock %}
|
|
1043
|
-
```
|
|
1044
|
-
|
|
1045
|
-
The stylesheet still styles `h2.section-title` and `p.front-links`, so an
|
|
1046
|
-
override can use both. Whatever the override prints, the solo author's
|
|
1047
|
-
`rel="me"` claims stay in the head of `/` (decision-45).
|
|
1048
|
-
|
|
1049
|
-
### An archive page
|
|
1050
|
-
|
|
1051
|
-
A page whose front matter says `archive: true` prints every post the site has
|
|
1052
|
-
published under its own words, newest month first:
|
|
1053
|
-
|
|
1054
|
-
```html
|
|
1055
|
-
<section class="archive">
|
|
1056
|
-
<h2>September 2026</h2>
|
|
1057
|
-
<ol class="list-none">
|
|
1058
|
-
<li>
|
|
1059
|
-
<a href="/2026/09/hello/"><span>Hello</span></a>
|
|
1060
|
-
</li>
|
|
1061
|
-
</ol>
|
|
1062
|
-
<h2>August 2026</h2>
|
|
1063
|
-
…
|
|
1064
|
-
</section>
|
|
1065
|
-
```
|
|
1066
|
-
|
|
1067
|
-
`partials/archive.njk` draws it and `layouts/page.njk` includes it. It is the
|
|
1068
|
-
whole archive on one page and nothing paginates it: an archive page is a way of
|
|
1069
|
-
finding one piece of writing rather than a listing to read through, which is
|
|
1070
|
-
also why there are no excerpts on it. Drafts and posts whose date has not
|
|
1071
|
-
arrived are absent, as they are everywhere else.
|
|
1072
|
-
|
|
1073
|
-
`archiveMonths` is what it loops over, and it is on the context **only** for a
|
|
1074
|
-
page whose front matter says `archive: true`, so a layout asks:
|
|
1075
|
-
|
|
1076
|
-
```njk
|
|
1077
|
-
{% if archiveMonths %}
|
|
1078
|
-
{% include "partials/archive.njk" %}
|
|
1079
|
-
{% endif %}
|
|
1080
|
-
```
|
|
1081
|
-
|
|
1082
|
-
Each entry of it is `{ month, posts }`: `month` is the heading, `September
|
|
1083
|
-
2026`, and `posts` is `{ title, url, date }` for each post of it, newest first.
|
|
1084
|
-
Which month a post belongs to is the CMS's answer rather than the template's,
|
|
1085
|
-
because a date in a file is a UTC instant and the site's `timezone` is the lens
|
|
1086
|
-
it is read through — so the grouping and the date printed under an entry are
|
|
1087
|
-
one decision rather than two.
|
|
1088
|
-
|
|
1089
|
-
The key is honoured wherever it is written, exactly as `contact: true` is: a
|
|
1090
|
-
theme that wants the list on some other kind of page adds the same three lines
|
|
1091
|
-
to that layout.
|
|
1092
|
-
|
|
1093
|
-
### The head
|
|
1094
|
-
|
|
1095
|
-
Beside the title, the canonical link and the feeds, every page carries a
|
|
1096
|
-
description, Open Graph and Twitter card tags, the site's icons and one
|
|
1097
|
-
JSON-LD graph. All of it is in the `head` block, so an override that only means
|
|
1098
|
-
to add a tag calls `{{ super() }}` first.
|
|
1099
|
-
|
|
1100
|
-
**The description** is the page's own `description` from the front matter, else
|
|
1101
|
-
the entry's `summary` — the line the feeds publish — else `site.tagline`. It is
|
|
1102
|
-
printed once, as `<meta name="description">`, and the Open Graph and Twitter
|
|
1103
|
-
descriptions say the same thing.
|
|
1104
|
-
|
|
1105
|
-
**The colours** come from the theme's `theme.json` (see
|
|
1106
|
-
[The manifest](#the-manifest)), on the context as `theme`.
|
|
1107
|
-
`<meta name="color-scheme">` comes before the stylesheet, so a reader in dark
|
|
1108
|
-
mode gets a dark canvas rather than a white flash while the stylesheet loads.
|
|
1109
|
-
`<meta name="theme-color">` is printed twice, once with
|
|
1110
|
-
`media="(prefers-color-scheme: light)"` and once for dark.
|
|
1111
|
-
|
|
1112
|
-
**The card.** `og:title` is the page's title, or the site's on the front page;
|
|
1113
|
-
`og:site_name` is always the site's. `og:type` is `article` on a rendered post
|
|
1114
|
-
or page and `website` everywhere else, a listing carrying a page's front matter
|
|
1115
|
-
included. `og:url` is the canonical URL: the page's own, or the `original` a
|
|
1116
|
-
post first published elsewhere names. An article also carries
|
|
1117
|
-
`article:published_time`, `article:modified_time` (the `updated` date, else the
|
|
1118
|
-
publish date), `article:author` (the author's profile URL here, else their
|
|
1119
|
-
name) and one `article:tag` per tag.
|
|
1120
|
-
|
|
1121
|
-
**The picture** is the first of these that the page has, on the context as
|
|
1122
|
-
`shareImage`:
|
|
1123
|
-
|
|
1124
|
-
1. The `image` in the entry's front matter.
|
|
1125
|
-
2. `site.avatar`, which only a hand edit of `site.json` sets.
|
|
1126
|
-
3. The profile photo of the person the page is by: the writer of a post or page
|
|
1127
|
-
that names an author, the user of an author archive, and the site's author
|
|
1128
|
-
on a solo-author site's homepage and other pages about nobody in particular.
|
|
1129
|
-
4. The site icon, from `icon` in `site.json`.
|
|
1130
|
-
|
|
1131
|
-
A page with none of these prints no `og:image` and no `twitter:image` rather
|
|
1132
|
-
than an empty one. The picture always carries `og:image:alt` and
|
|
1133
|
-
`twitter:image:alt`. The alt text is what the media library says about the
|
|
1134
|
-
upload, else words for what it shows. For the entry's own picture, its
|
|
1135
|
-
`imageAlt` front matter comes first, and the entry's title last. For the
|
|
1136
|
-
avatar, the last resort is the site's author; for a photo, that person's
|
|
1137
|
-
display name; for the icon, the site's title. `og:image:width` and
|
|
1138
|
-
`og:image:height` are printed when the image's variants have been derived,
|
|
1139
|
-
because the size is read from their sidecar rather than from the file.
|
|
1140
|
-
`twitter:card` is `summary_large_image` for a picture at least 1200 pixels wide
|
|
1141
|
-
and wider than it is tall. Everything else, a square photo included, is
|
|
1142
|
-
`summary`, the small picture beside the words.
|
|
1143
|
-
|
|
1144
|
-
**The icons** come from the site's `icon` setting in `site.json`, else
|
|
1145
|
-
`site.avatar`, through the derived images (decision-10). `icon` exists for a
|
|
1146
|
-
site whose avatar is a face and whose mark is a logo. The head links `icon` at
|
|
1147
|
-
32 and 16 pixels and `apple-touch-icon` at 180, each a square PNG cropped from
|
|
1148
|
-
the middle of the picture and encoded the first time a browser asks for it. An
|
|
1149
|
-
SVG icon is linked first as itself, with `type="image/svg+xml"` and no
|
|
1150
|
-
`sizes`, and the PNGs are rasterised from it. They are on the context as
|
|
1151
|
-
`icons`, a list of `{ rel, sizes, type, href }`, which is empty, and the links
|
|
1152
|
-
are not printed at all, when the site has neither setting, when it names a
|
|
1153
|
-
file no icon can be made of, or when image optimization is off.
|
|
1154
|
-
|
|
1155
|
-
**The favicon and the manifest** are routes, not templates. `/favicon.ico`
|
|
1156
|
-
holds 16, 32 and 48 pixel frames drawn from the same picture, because browsers
|
|
1157
|
-
and crawlers ask the root for it whatever the head links; it answers 404 on a
|
|
1158
|
-
site with no icon. `/manifest.webmanifest` gives the site's title as `name` and
|
|
1159
|
-
`short_name`, `start_url` `/`, `display` `minimal-ui`, the theme's light
|
|
1160
|
-
`themeColor` as `theme_color` and `background_color`, and icons at 192 and 512
|
|
1161
|
-
pixels plus a maskable 512 whose picture sits inside the middle 80%, padded
|
|
1162
|
-
with its own dominant colour. The head links it on every page.
|
|
1163
|
-
|
|
1164
|
-
**The search description** is a route too. `/opensearch.xml` is an OpenSearch
|
|
1165
|
-
1.1 description that names the site by its title (cut to the 16 characters
|
|
1166
|
-
`ShortName` allows), describes it by its tagline, else its title, shows
|
|
1167
|
-
`/favicon.ico` as its `Image` when the site has an icon, and searches
|
|
1168
|
-
`/search/?q={searchTerms}`. Every URL in it is absolute and carries the base
|
|
1169
|
-
path. The head links it on every page with `<link rel="search"
|
|
1170
|
-
type="application/opensearchdescription+xml">`, outside the `head` block, so a
|
|
1171
|
-
browser offers the site as a search engine. A theme that replaces `base.njk`
|
|
1172
|
-
copies that line to keep the offer.
|
|
1173
|
-
|
|
1174
|
-
**The embed card** is a route too. `/_geekity/oembed?url=…` is an oEmbed
|
|
1175
|
-
provider: for the URL of a published post or page it answers a `rich` embed
|
|
1176
|
-
with the title, the author and their archive, the site as provider, a
|
|
1177
|
-
thumbnail when the post's own `image` has a recorded size, and an `html`
|
|
1178
|
-
card that is a blockquote of escaped text and links with no script, followed
|
|
1179
|
-
by a hidden, sandboxed `<iframe>` of `/_geekity/embed?url=…`. That route is
|
|
1180
|
-
the same card as a page of its own, and it is the one response another site may
|
|
1181
|
-
frame. WordPress keeps a card from a provider it does not know only when the
|
|
1182
|
-
card has an iframe, and it shows the frame once the page inside reports its
|
|
1183
|
-
height. A consumer that strips iframes still has the blockquote. `maxwidth`
|
|
1184
|
-
and `maxheight` are honoured, `format=xml` gives the same fields as XML, and
|
|
1185
|
-
any other URL is a 404. The head of each post and page links it with
|
|
1186
|
-
`<link rel="alternate" type="application/json+oembed">` and its `text/xml+oembed`
|
|
1187
|
-
twin, outside the `head` block. A theme that replaces `base.njk` copies those
|
|
1188
|
-
lines to keep the card.
|
|
1189
|
-
|
|
1190
|
-
**The structured data** is `partials/jsonld.njk`, one `<script
|
|
1191
|
-
type="application/ld+json">` holding one `@graph` per page, and it is the only
|
|
1192
|
-
structured data the theme emits — there is no Microdata anywhere, by
|
|
1193
|
-
decision-16, because the visible markup already carries microformats2 for the
|
|
1194
|
-
IndieWeb. The graph holds:
|
|
1195
|
-
|
|
1196
|
-
- `WebSite`, always, with the site's title, tagline and URL, and a
|
|
1197
|
-
`potentialAction` that is a `SearchAction` on
|
|
1198
|
-
`/search/?q={search_term_string}`. On a site whose author is a user, its
|
|
1199
|
-
`publisher` and `about` both point at that user's Person, on every page. On
|
|
1200
|
-
a site with several authors its `publisher` is the Organization below and it
|
|
1201
|
-
has no `about`. With a site license, `license` is its URL.
|
|
1202
|
-
- `Organization`, on a site with several authors only: the site itself, named
|
|
1203
|
-
for its title, at `{baseUrl}/#organization`. It publishes the `WebSite` and
|
|
1204
|
-
every entry.
|
|
1205
|
-
- `Person`, from `siteAuthor`: their name, archive URL, avatar, bio, job title
|
|
1206
|
-
and location, and a `sameAs` of their profile links and their actor id, which
|
|
1207
|
-
is what asserts that the schema.org Person and the fediverse actor are one
|
|
1208
|
-
identity. Identity comes from a user profile, so a site — or a byline —
|
|
1209
|
-
naming nobody with an account here prints no Person, and the `author`
|
|
1210
|
-
reference goes with it. A site with several authors prints no Person on a
|
|
1211
|
-
page about nobody in particular.
|
|
1212
|
-
- `ProfilePage` on an author archive, whose `mainEntity` is that Person.
|
|
1213
|
-
- `BlogPosting` on a post and `Article` on a page, with the headline, URL,
|
|
1214
|
-
`mainEntityOfPage`, `datePublished`, `dateModified`, description, image,
|
|
1215
|
-
`author`, and the site's `publisher`: the site author's Person, or the
|
|
1216
|
-
Organization. A post with no title is headed by the words its `<title>`
|
|
1217
|
-
names it by, its `label`, cut on a word boundary to at most 110 characters.
|
|
1218
|
-
A post with no words either is labelled by what it is (TASK-261): "Liked",
|
|
1219
|
-
"Reposted", "Bookmarked" or "Reply to" and the cited page's stored title,
|
|
1220
|
-
else the host its citation line names ("Liked a page on www.youtube.com"), and
|
|
1221
|
-
for a photo post its first photo's alt text, cut to ten words like a note's
|
|
1222
|
-
(TASK-264), or "Photo" when that photo has none. The kicker still says Photo
|
|
1223
|
-
and no heading is printed. "Untitled" is the last fallback.
|
|
1224
|
-
Its `image` is the picture the Open Graph tags print, so the two always
|
|
1225
|
-
agree (see [The head](#the-head)). Its `license` is the page's license URL,
|
|
1226
|
-
the front matter's over the site's, and absent with none.
|
|
1227
|
-
- `BreadcrumbList` wherever the page prints a breadcrumb (see
|
|
1228
|
-
[The page shell](#the-page-shell)): one `ListItem` per crumb with its
|
|
1229
|
-
`position`, `name` and absolute URL as `item`.
|
|
1230
|
-
|
|
1231
|
-
A site that wants a different graph — more types, an `Organization` publisher,
|
|
1232
|
-
nothing at all — writes its own `partials/jsonld.njk` and that file replaces
|
|
1233
|
-
this one, like any other partial.
|
|
1234
|
-
|
|
1235
|
-
### Prefetching and page transitions
|
|
1236
|
-
|
|
1237
|
-
Two tags near the top of `<head>` make moving between pages feel faster. Both
|
|
1238
|
-
are progressive enhancement: a browser that does not support one ignores it.
|
|
1239
|
-
|
|
1240
|
-
**Speculation rules.** The `speculationRules` block prints one `<script
|
|
1241
|
-
type="speculationrules">` that asks the browser to prefetch a page the reader
|
|
1242
|
-
is about to open. It covers links to this site's own pages, at `moderate`
|
|
1243
|
-
eagerness, which in Chrome is a hover of about 200 milliseconds or the start of
|
|
1244
|
-
a tap. It never prefetches:
|
|
1245
|
-
|
|
1246
|
-
- `/admin` and anything under `/admin/`, which includes `/admin/logout`.
|
|
1247
|
-
- Any path with `logout` in it.
|
|
1248
|
-
- A feed: `/feed/`, `/feed/atom/`, `/feed/json/`, and any other path that has
|
|
1249
|
-
a `/feed/` segment, such as a tag's feed or a post's comments feed.
|
|
1250
|
-
- A file under `/uploads/`.
|
|
1251
|
-
- A URL with a query, such as `/search/?q=words`.
|
|
1252
|
-
|
|
1253
|
-
Every pattern is written through the `url` filter, so a site served from a
|
|
1254
|
-
subdirectory prefetches only its own pages.
|
|
1255
|
-
|
|
1256
|
-
**No rules reach a signed-in reader.** The CMS removes every `<script
|
|
1257
|
-
type="speculationrules">` from a page it draws for somebody signed in, the same
|
|
1258
|
-
page that carries [the admin bar](#the-admin-bar). That page is drawn for one
|
|
1259
|
-
person and never stored, so a prefetch of it is wasted work. A site theme that
|
|
1260
|
-
writes its own rules does not have to check who is reading.
|
|
1261
|
-
|
|
1262
|
-
**View transitions.** The `viewTransitions` block prints a `<style>` that opts
|
|
1263
|
-
into cross-document view transitions. The browser cross-fades from one page of
|
|
1264
|
-
the site to the next, with no JavaScript. The rule is inside `@media
|
|
1265
|
-
(prefers-reduced-motion: no-preference)`, so a reader who asks for reduced
|
|
1266
|
-
motion gets no transition:
|
|
1267
|
-
|
|
1268
|
-
```css
|
|
1269
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
1270
|
-
@view-transition {
|
|
1271
|
-
navigation: auto;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
```
|
|
1275
|
-
|
|
1276
|
-
A site theme that wants a different animation adds `::view-transition-*` rules
|
|
1277
|
-
to its own stylesheet.
|
|
1278
|
-
|
|
1279
|
-
**Turning either off.** Override its block with nothing in it:
|
|
1280
|
-
|
|
1281
|
-
```njk
|
|
1282
|
-
{% extends "layouts/base.njk" %}
|
|
1283
|
-
|
|
1284
|
-
{% block speculationRules %}{% endblock %}
|
|
1285
|
-
{% block viewTransitions %}{% endblock %}
|
|
1286
|
-
```
|
|
1287
|
-
|
|
1288
|
-
### Colours
|
|
1289
|
-
|
|
1290
|
-
Every colour is a custom property on `:root`, and these tokens are the
|
|
1291
|
-
override contract: a site that only wants different colours redefines them in
|
|
1292
|
-
its own stylesheet rather than replacing this one. Tailwind's own palette is
|
|
1293
|
-
cleared in `src/style.css`, and its utilities (`text-ink`, `bg-paper`,
|
|
1294
|
-
`border-edge` and the rest) are mapped onto these properties with
|
|
1295
|
-
`@theme inline`, so redefining a token recolours every rule that uses it.
|
|
1296
|
-
|
|
1297
|
-
A second scheme under `@media (prefers-color-scheme: dark)` redefines the same
|
|
1298
|
-
tokens on dark paper, and `color-scheme: light dark` lets a browser paint its
|
|
1299
|
-
own form controls and scrollbars to match. There is no toggle: the reader's
|
|
1300
|
-
system setting is the setting.
|
|
1301
|
-
|
|
1302
|
-
Both schemes meet the Colour contrast rule of the
|
|
1303
|
-
[specification.website checklist](https://specification.website/checklist.md):
|
|
1304
|
-
every run of text, the small kickers, dates and meta lines included, at 7:1
|
|
1305
|
-
(WCAG 1.4.6, AAA) on whatever it sits on, and every border a reader relies on
|
|
1306
|
-
(a field, a card, a code block, the focus outline, the citation and blockquote
|
|
1307
|
-
rules) at 3:1 (1.4.11). Nothing a reader needs is drawn at reduced opacity.
|
|
1308
|
-
That is a test rather than a claim: `src/web/theme-colors.test.ts` reads the
|
|
1309
|
-
tokens out of the compiled stylesheet and computes the ratio of every pair the
|
|
1310
|
-
design puts on screen, so a colour changed in `src/style.css` that breaks one
|
|
1311
|
-
fails the build.
|
|
1312
|
-
|
|
1313
|
-
| Token | Utility | What it colours |
|
|
1314
|
-
| ------------------------- | ---------- | ---------------------------------------------------------------------- |
|
|
1315
|
-
| `--color-body` | `paper` | The paper, and the words on a submit button. |
|
|
1316
|
-
| `--color-text` | `ink` | Body text and headlines. |
|
|
1317
|
-
| `--color-muted` | `muted` | Kickers, dates, meta lines, decks, quotes, the bio note, the footer. |
|
|
1318
|
-
| `--color-primary` | `accent` | Links, a kicker's kind word, the citation rule, the submit button. |
|
|
1319
|
-
| `--color-secondary` | `accent-2` | Tag links and the focus outline. |
|
|
1320
|
-
| `--color-rule` | `rule` | Decorative hairlines only: between entries, above the footer. |
|
|
1321
|
-
| `--color-edge` | `edge` | Borders a reader relies on: fields, the previous and next cards, code. |
|
|
1322
|
-
| `--color-base` | `surface` | A sunk surface: fields, notices, a face with no avatar. |
|
|
1323
|
-
| `--color-base-2` | — | A cooler sunk surface. |
|
|
1324
|
-
| `--color-base-3` | — | The raised surface: the focused skip link. |
|
|
1325
|
-
| `--color-code-background` | `code` | Behind `code` and a fenced block. |
|
|
1326
|
-
| `--color-code-text` | — | Code with no highlighting on it. |
|
|
1327
|
-
| `--color-error` | `error` | A field that will not do, and what the form says about it. |
|
|
1328
|
-
|
|
1329
|
-
`--color-rule` is the one token in no contrast pair, because it is decoration:
|
|
1330
|
-
a site that recolours it may make it as faint as it likes. Anything drawn with
|
|
1331
|
-
`--color-edge` has to stay at 3:1 against the paper and the surface.
|
|
1332
|
-
|
|
1333
|
-
### Code highlighting
|
|
1334
|
-
|
|
1335
|
-
The CMS renders a fenced block as `<pre tabindex="0"><code
|
|
1336
|
-
class="language-x">` and highlights nothing: highlighting is a theme's, and it
|
|
1337
|
-
happens on the client (decision-16). The `tabindex` is the core's doing and is
|
|
1338
|
-
there on every `<pre>`, because a block wider than the measure scrolls sideways
|
|
1339
|
-
and a scroll container nothing can focus cannot be read from a keyboard.
|
|
1340
|
-
|
|
1341
|
-
This theme highlights with **[highlight.js](https://highlightjs.org/)**,
|
|
1342
|
-
self-hosted at `static/highlight.js` and pinned — nothing is fetched from a
|
|
1343
|
-
CDN. The `scripts` block of `layouts/base.njk` loads it, deferred, **only on a
|
|
1344
|
-
page whose rendered body holds a `language-` class**, so a page with no code on
|
|
1345
|
-
it ships no JavaScript at all. Auto-detection is off: a block with no language,
|
|
1346
|
-
or one naming a language the bundle does not carry, is left exactly as the
|
|
1347
|
-
renderer wrote it — its class, its plain text, no highlighting.
|
|
1348
|
-
|
|
1349
|
-
The bundle carries the core and eighteen grammars — `bash`, `css`, `diff`,
|
|
1350
|
-
`dockerfile`, `go`, `ini`, `javascript`, `json`, `markdown`, `nginx`, `php`,
|
|
1351
|
-
`python`, `rust`, `shell`, `sql`, `typescript`, `xml` and `yaml`, with their
|
|
1352
|
-
aliases, so `js`, `ts`, `html`, `yml` and `sh` all work. It is a build product
|
|
1353
|
-
that is nevertheless committed, so a site gets a working highlighter out of the
|
|
1354
|
-
package with no build step. Rebuilding it is one command:
|
|
1355
|
-
|
|
1356
|
-
```
|
|
1357
|
-
pnpm --filter @geekity/cms build:highlight
|
|
1358
|
-
```
|
|
1359
|
-
|
|
1360
|
-
Run that after bumping the `highlight.js` devDependency or editing the language
|
|
1361
|
-
list in `scripts/build-highlight.js`, and commit what it writes.
|
|
1362
|
-
`src/web/highlight-bundle.test.ts` fails when the committed file was built from
|
|
1363
|
-
a different version of highlight.js than the one installed.
|
|
1364
|
-
|
|
1365
|
-
**The palette is Tomorrow**, the one both of the sites this design comes from
|
|
1366
|
-
used: Chris Kempson's Tomorrow on light paper and Tomorrow Night on dark, from
|
|
1367
|
-
highlight.js's own base16 themes, which is also what decides which `hljs-`
|
|
1368
|
-
scope takes which slot. None of the light colours is the stock one: a syntax
|
|
1369
|
-
theme is drawn for an editor, where a faint comment is a feature, and here code
|
|
1370
|
-
is text like any other and meets 7:1 on its block, the bar every run of text
|
|
1371
|
-
on the page meets. Each light colour was darkened along its own hue until it
|
|
1372
|
-
did and no further, and four of the dark ones (the comment grey, the red, the
|
|
1373
|
-
blue and the `base0F` brown) were lightened toward white the same way. Every one
|
|
1374
|
-
of them is in the contrast test beside the rest of the palette.
|
|
1375
|
-
|
|
1376
|
-
| Token | base16 | What it colours |
|
|
1377
|
-
| --------------------------------- | -------- | ------------------------------------------ |
|
|
1378
|
-
| `--color-code-comment` | `base03` | Comments. |
|
|
1379
|
-
| `--color-code-tag` | `base04` | Markup tags. |
|
|
1380
|
-
| `--color-code-name` | `base08` | Variables, names, selectors, list bullets. |
|
|
1381
|
-
| `--color-code-literal` | `base09` | Numbers, constants, attributes, links. |
|
|
1382
|
-
| `--color-code-class` | `base0A` | Class names, bold. |
|
|
1383
|
-
| `--color-code-string` | `base0B` | Strings and inline code. |
|
|
1384
|
-
| `--color-code-support` | `base0C` | Built-ins, regexps, quotes. |
|
|
1385
|
-
| `--color-code-function` | `base0D` | Function names, headings, attributes. |
|
|
1386
|
-
| `--color-code-keyword` | `base0E` | Keywords, types, italic. |
|
|
1387
|
-
| `--color-code-meta` | `base0F` | Preprocessor and embedded-language lines. |
|
|
1388
|
-
| `--color-code-added-background` | — | An added line in a diff. |
|
|
1389
|
-
| `--color-code-removed-background` | — | A removed line in a diff. |
|
|
1390
|
-
|
|
1391
|
-
Delimiters, operators and substitutions are deliberately `--color-code-text`,
|
|
1392
|
-
the block's own ink: they are most of the characters on screen and a colour
|
|
1393
|
-
there is noise. A `diff` block reads by the line rather than by the token, the
|
|
1394
|
-
way the source site's `prism-diff.css` did: `.hljs-addition` and
|
|
1395
|
-
`.hljs-deletion` take a tint out to the edges of the block and keep the ink, so
|
|
1396
|
-
the `+` and the `-` in the text carry the meaning as well as the colour does.
|
|
1397
|
-
|
|
1398
|
-
**Swapping it.** Four sizes of change, smallest first:
|
|
1399
|
-
|
|
1400
|
-
- _Another palette, same highlighter._ Redefine the dozen `--color-code-*`
|
|
1401
|
-
tokens. Note that `static/style.css` is an all-or-nothing override: a theme
|
|
1402
|
-
that ships its own stylesheet owns these rules too, and a fenced block on it
|
|
1403
|
-
is unhighlighted until it writes them.
|
|
1404
|
-
- _Other languages._ Edit `LANGUAGES` in `scripts/build-highlight.js`, rebuild,
|
|
1405
|
-
and commit — or put your own `static/highlight.js` in your theme, which the
|
|
1406
|
-
`/theme/` route resolves before the packaged one.
|
|
1407
|
-
- _Another highlighter._ Override the `scripts` block with your own tag. The
|
|
1408
|
-
markup is `pre > code.language-x`, which is what every highlighter reads.
|
|
1409
|
-
- _None._ Override the `scripts` block with nothing in it.
|
|
1410
|
-
|
|
1411
|
-
```njk
|
|
1412
|
-
{% extends "layouts/base.njk" %}
|
|
1413
|
-
|
|
1414
|
-
{% block scripts %}{% endblock %}
|
|
1415
|
-
```
|
|
1416
|
-
|
|
1417
|
-
### Videos
|
|
1418
|
-
|
|
1419
|
-
A YouTube or Vimeo URL on a line of its own in a post plays as the video
|
|
1420
|
-
(decision-34; doc-2 lists the addresses it knows). The core renders it into the
|
|
1421
|
-
body, so it reaches a theme inside `content` as:
|
|
1422
|
-
|
|
1423
|
-
```html
|
|
1424
|
-
<figure class="video-embed video-embed-youtube">
|
|
1425
|
-
<iframe
|
|
1426
|
-
src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
|
|
1427
|
-
width="560"
|
|
1428
|
-
height="315"
|
|
1429
|
-
title="YouTube video"
|
|
1430
|
-
loading="lazy"
|
|
1431
|
-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
1432
|
-
referrerpolicy="strict-origin-when-cross-origin"
|
|
1433
|
-
allowfullscreen
|
|
1434
|
-
></iframe>
|
|
1435
|
-
<figcaption>
|
|
1436
|
-
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
|
1437
|
-
>https://www.youtube.com/watch?v=dQw4w9WgXcQ</a
|
|
1438
|
-
>
|
|
1439
|
-
</figcaption>
|
|
1440
|
-
</figure>
|
|
1441
|
-
```
|
|
1442
|
-
|
|
1443
|
-
It is one line in the real output. A Vimeo video is the same with
|
|
1444
|
-
`video-embed-vimeo`, a `https://player.vimeo.com/video/ID?dnt=1` player and the
|
|
1445
|
-
title `Vimeo video`. The core sets no style on it: with nothing from the theme
|
|
1446
|
-
the player is the providers' standard 560 by 315.
|
|
1447
|
-
|
|
1448
|
-
**Paper** makes it responsive. `.video-embed iframe` takes the full width of
|
|
1449
|
-
the text column at 16:9, with the rounded corners an image has, and the
|
|
1450
|
-
figcaption takes the muted caption style every figure in a post has. A theme
|
|
1451
|
-
that ships its own `static/style.css` writes that rule itself:
|
|
1452
|
-
|
|
1453
|
-
```css
|
|
1454
|
-
.video-embed iframe {
|
|
1455
|
-
width: 100%;
|
|
1456
|
-
height: auto;
|
|
1457
|
-
aspect-ratio: 16 / 9;
|
|
1458
|
-
}
|
|
1459
|
-
```
|
|
1460
|
-
|
|
1461
|
-
The figcaption is the link a feed reader, a fediverse server or the embed view
|
|
1462
|
-
keeps when it drops the iframe. A theme that would rather not show it on the
|
|
1463
|
-
page can hide `.video-embed figcaption`; the feeds carry it either way.
|
|
1464
|
-
|
|
1465
|
-
## Mail templates
|
|
1466
|
-
|
|
1467
|
-
The messages the CMS sends live under `mail/` and resolve the same way, so a
|
|
1468
|
-
`mail/test.txt.njk` in the theme the site wears replaces the text of the test
|
|
1469
|
-
message and leaves its subject and HTML twin coming from the package. Each message is up to three
|
|
1470
|
-
files:
|
|
1471
|
-
|
|
1472
|
-
| File | What it is |
|
|
1473
|
-
| ------------------------- | -------------------------------------------------------------- |
|
|
1474
|
-
| `mail/<name>.subject.njk` | The subject. Rendered to a single line. Optional. |
|
|
1475
|
-
| `mail/<name>.txt.njk` | The plain text body. **Required.** |
|
|
1476
|
-
| `mail/<name>.html.njk` | The HTML twin. Optional; without it the message is plain text. |
|
|
1477
|
-
|
|
1478
|
-
A message that has no `.txt.njk` anywhere on the search path is an error rather
|
|
1479
|
-
than an empty email, so a typo in a template name is reported instead of sent.
|
|
1480
|
-
|
|
1481
|
-
The package ships seven messages:
|
|
1482
|
-
|
|
1483
|
-
| Name | When it goes | What `data` carries |
|
|
1484
|
-
| ------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1485
|
-
| `test` | Send test email, on Settings. | Nothing. |
|
|
1486
|
-
| `password-reset` | Somebody asked to reset a password. | `username`, `resetUrl`, `expiresAt`, `expiresInHours`. |
|
|
1487
|
-
| `password-changed` | A reset link was used. | `username`, `signedOut` (how many sessions ended). |
|
|
1488
|
-
| `comment-pending` | A comment or webmention is waiting for approval. | `comment`, `post` (`title`, `url`), `actions` (one `{ action, label, url }` each for approve, spam and delete), `queueUrl`. |
|
|
1489
|
-
| `comment-digest` | An hourly or daily digest of what is waiting. | `items` (one `{ comment, post, actions }` each), `total` waiting, `more` beyond the ones listed, the `mode` that asked for it, `queueUrl`. |
|
|
1490
|
-
| `comment-reply` | A reply to somebody's comment was approved. | `reply`, `comment` (the one it answers), `post`, `unsubscribeUrl`. |
|
|
1491
|
-
| `contact-message` | Somebody filled in a page's contact form. | `message` (`id`, `subject`, `text`, `received`), `from` (`name`, `email`), `page` (`slug`, `permalink`, `title`, `url`), `messagesUrl`. |
|
|
1492
|
-
|
|
1493
|
-
The `comment` inside each of a digest's `items` is the same shape as
|
|
1494
|
-
`comment-pending`'s, and its `actions` are the same three links, minted per
|
|
1495
|
-
recipient because each is spent the first time it is used. A digest is built
|
|
1496
|
-
from the queue as it stands when it is sent, so an item moderated in the
|
|
1497
|
-
meantime is simply not in it, and no digest is sent to somebody with an empty
|
|
1498
|
-
queue.
|
|
1499
|
-
|
|
1500
|
-
`comment` and `reply` above are the same shape: `author`, `website`, `source`
|
|
1501
|
-
(`comment` or `webmention`), `kind`, `text`, `html`, `submitted`, and `url`.
|
|
1502
|
-
The commenter's email address is deliberately **not** among them — the message
|
|
1503
|
-
goes to a moderator, but it also goes through a mail provider's servers, and
|
|
1504
|
-
nothing on that screen needs it.
|
|
1505
|
-
|
|
1506
|
-
The links in `actions` and `unsubscribeUrl` each carry a signed token and work
|
|
1507
|
-
without a login. Print them; do not try to build one.
|
|
1508
|
-
|
|
1509
|
-
`contact-message` is sent with reply-to set to the sender, so answering it in a
|
|
1510
|
-
mail reader answers the person who wrote it. `message.text` is plain text as it
|
|
1511
|
-
was typed — print it, do not render it as Markdown or as HTML.
|
|
1512
|
-
|
|
1513
|
-
The subject and the plain text body render with autoescaping **off**, because
|
|
1514
|
-
neither is HTML: `&` between two query parameters stays an `&`, and an
|
|
1515
|
-
apostrophe in somebody's name stays an apostrophe. The HTML twin escapes as
|
|
1516
|
-
every other template does.
|
|
1517
|
-
|
|
1518
|
-
The context is `site` (that is `content/_data/site.json`), `baseUrl`, and
|
|
1519
|
-
whatever the feature that sent it passed as `data`. The `date`, `url` and
|
|
1520
|
-
`absoluteUrl` filters are the same ones a page has, so a message can write a
|
|
1521
|
-
link with `{{ "/admin/" | absoluteUrl }}`.
|
|
1522
|
-
|
|
1523
|
-
Writing `mail/welcome.txt.njk` into the theme is enough to add a message this
|
|
1524
|
-
package never shipped; nothing has to be registered.
|
|
1525
|
-
|
|
1526
|
-
## Template context
|
|
1527
|
-
|
|
1528
|
-
The context mirrors what an Eleventy layout receives, so a layout ported from
|
|
1529
|
-
an Eleventy build needs few edits. It is part of the package's semver contract.
|
|
1530
|
-
|
|
1531
|
-
Every template gets:
|
|
1532
|
-
|
|
1533
|
-
| Key | What it holds |
|
|
1534
|
-
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1535
|
-
| `site` | `content/_data/site.json`, if the site has one, over the defaults `title` and `url`. Any key in the file is readable, so `site.tagline`, `site.author` and anything else a site adds are all available. |
|
|
1536
|
-
| `menus` | Every menu the site stores, by name, marked for this page: `menus.primary`, `menus.footer`, and any other name. See [Navigation](#navigation). |
|
|
1537
|
-
| `siteAuthor` | Who the page is by, as a profile. **Absent** when nobody matches. See [Bylines and author archives](#bylines-and-author-archives). |
|
|
1538
|
-
| `soloAuthor` | The site author's profile, on every page of a site whose Site author setting names a user. **Absent** on a site with several authors. See [The bio](#the-bio). |
|
|
1539
|
-
| `icons` | The site's icons, as `{ rel, sizes, type, href }`. Empty until the site has an icon or an avatar to derive them from. See [The head](#the-head). |
|
|
1540
|
-
| `theme` | The colours the theme declares, as `{ colorScheme, themeColor: { light, dark } }`, each absent when no theme declares it. See [The manifest](#the-manifest). |
|
|
1541
|
-
| `shareImage` | The picture a shared link shows, as `{ url, alt, size: { width, height }, card }`. `size` is absent when it is not known. **Absent** when there is no picture. See [The head](#the-head). |
|
|
1542
|
-
| `llmsTxt` | The path of `/llms.txt`, while the site serves it. **Absent** when the site has turned it off. The default theme links it from the front page only. |
|
|
1543
|
-
| `license` | The page's license as `{ name, url }`: the page's `license` front matter, else the site's. **Undefined** with none. It replaces the raw front matter value on the context. |
|
|
1544
|
-
| `siteLicense` | The site's own license as `{ name, url }`, whatever the page says. **Undefined** with none. |
|
|
1545
|
-
|
|
1546
|
-
A document — one post, one page, or one entry of a listing — adds:
|
|
1547
|
-
|
|
1548
|
-
| Key | What it holds |
|
|
1549
|
-
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
1550
|
-
| `title` | Display title. |
|
|
1551
|
-
| `date` | Publish date, a JavaScript `Date` at the UTC instant the file holds. Absent when the document has none. |
|
|
1552
|
-
| `updated` | Last modified date, a `Date`, when the front matter has one. |
|
|
1553
|
-
| `tags` | The document's tags, in file order. |
|
|
1554
|
-
| `categories` | The document's categories, in file order. |
|
|
1555
|
-
| `content` | The Markdown body rendered to HTML. Print it with `\| safe`. |
|
|
1556
|
-
| `summary` | Its `description` as plain text, else an excerpt of the body, else empty. The line the feeds publish. |
|
|
1557
|
-
| `url` | The document's URL path, the same value as `page.url`. |
|
|
1558
|
-
| `page.url` | The document's URL path. Always ends in `/`. |
|
|
1559
|
-
| `page.date` | The same `Date` as `date`. |
|
|
1560
|
-
| `page.fileSlug` | The permalink's last segment. |
|
|
1561
|
-
| `page.inputPath` | The source file, relative to the content directory. |
|
|
1562
|
-
| `type` | `post` or `page`. |
|
|
1563
|
-
| `permalink`, `slug`, `draft`, `description` | Straight from the front matter. |
|
|
1564
|
-
| `lang` | The `lang` front matter as a canonical tag, such as `fr-CA`. Absent when it names none or no tag. |
|
|
1565
|
-
| `enclosure` | The post's recording, parsed, with `player` (`audio` or `video`) and `transcript.captions`. Absent with none. |
|
|
1566
|
-
| `photos` | The post's photos, each `{ url, alt, html }`; `html` is the `img.u-photo`, responsive. Empty with none. |
|
|
1567
|
-
| `citations` | What it reposts, likes or bookmarks, each `{ property, url, context }`; `context` as `replyContext`, when known. |
|
|
1568
|
-
| `read` | A read post's `{ status, statusLabel, of, line }`; `of` is `{ name, author, uid, uidLabel, url }`. |
|
|
1569
|
-
| `rsvp` | An RSVP's `{ value, label, line }`: `yes`, `no`, `maybe` or `interested`, its words, and its `p-rsvp` line. |
|
|
1570
|
-
| `event` | An event's `{ start, end, location }`: `Date`s, and `{ kind: "place", name }` or `{ kind: "virtual", url }`. |
|
|
1571
|
-
| `syndicateTo` | On a post's page, the syndication targets it selects, each `{ id, name, url }`. Empty with none. |
|
|
1572
|
-
| `syndication` | Its copies elsewhere, each `{ url, label }`: front matter `syndication`, and on its page the copies targets made. |
|
|
1573
|
-
| `original` | Where it was first published, `{ url, label }`: `canonical_href`, named by `canonical_name` or its host. |
|
|
1574
|
-
| `author` | Who wrote it, as a profile rather than a string. See [Bylines and author archives](#bylines-and-author-archives). |
|
|
1575
|
-
| `activityStreams` | The post's ActivityPub object id, absolute. Only on a rendered published post. |
|
|
1576
|
-
| `previous` | The published post before this one by date, as `{ title, url }`. Absent on the oldest post. |
|
|
1577
|
-
| `next` | The published post after it. Absent on the newest post, and on a page. |
|
|
1578
|
-
| `backlinks` | The site's listed posts and pages that link to it, newest first, each `{ title, url, date }`. Empty with none. |
|
|
1579
|
-
| `newestPosts` | A function, front page only: `newestPosts(5)` is the five newest published posts, as entries. |
|
|
1580
|
-
| `postsPage` | The page carrying the listing, as `{ title, url }`, on the front page only. Absent when the site names none. |
|
|
1581
|
-
| `archiveMonths` | Every published post as `{ month, posts }`, newest month first. Only on a page that says `archive: true`. |
|
|
1582
|
-
| `webmention` | Where a webmention about this page is sent. Only on a rendered document, and only while the site takes them. |
|
|
1583
|
-
| `noindex` | `true` on an unlisted or unpublished page, which the base layout prints as a robots `noindex` meta. Else absent. |
|
|
1584
|
-
| `conversation` | The replies, likes and boosts under the post. Only when there are any. See [The conversation](#the-conversation). |
|
|
1585
|
-
| everything else | Any front matter key the CMS does not model is on the context under its own name. |
|
|
1586
|
-
|
|
1587
|
-
A listing — the home page, a tag archive, a category archive or an author
|
|
1588
|
-
archive — adds:
|
|
1589
|
-
|
|
1590
|
-
| Key | What it holds |
|
|
1591
|
-
| ------------------------------------------ | --------------------------------------------------------------------------------- |
|
|
1592
|
-
| `title` | The site title on the home page, the term on a taxonomy archive. |
|
|
1593
|
-
| `posts` | The documents on this page of the listing, newest first, each in the shape above. |
|
|
1594
|
-
| `pagination.items` | The same array, under the name Eleventy gives it. |
|
|
1595
|
-
| `pagination.pageNumber` | Zero-based index of this page, as in Eleventy. |
|
|
1596
|
-
| `pagination.totalPages`, `.total`, `.size` | How many pages, how many documents, how many per page. |
|
|
1597
|
-
| `pagination.href.first/last/next/previous` | Pager URLs. `next` and `previous` are `null` at the ends. |
|
|
1598
|
-
| `pagination.pages` | Every page's URL, in order. |
|
|
1599
|
-
| `tag` | The tag, on a tag archive only. |
|
|
1600
|
-
| `category` | The category, on a category archive only. |
|
|
1601
|
-
| `author` | The person, on an author archive only. Same shape as a post's `author`. |
|
|
1602
|
-
|
|
1603
|
-
How many posts a listing page holds comes from `postsPerPage` in
|
|
1604
|
-
`content/_data/site.json`, and defaults to 10. How many entries a feed holds
|
|
1605
|
-
comes from `feedSize` in the same file, and defaults to 20. The `<html lang>`
|
|
1606
|
-
comes from `language`, and defaults to `en`. A document whose `lang` names
|
|
1607
|
-
another language carries it on its own `<article>`, in an entry, a listing and
|
|
1608
|
-
search results alike, through `partials/lang.njk`, and the dates inside that
|
|
1609
|
-
article are written in it (see the `date` filter). The rssCloud and WebSub server the
|
|
1610
|
-
feeds advertise comes from `notifyServer`, and is empty for none; the theme
|
|
1611
|
-
writes nothing for it, because a cloud is advertised in the feed rather than on
|
|
1612
|
-
the page.
|
|
1613
|
-
|
|
1614
|
-
## Bylines and author archives
|
|
1615
|
-
|
|
1616
|
-
`author` on a document is the person the front matter names, resolved against
|
|
1617
|
-
the site's users. The fields come from their profile on **Users** in the admin,
|
|
1618
|
-
which is the one place identity is written:
|
|
1619
|
-
|
|
1620
|
-
| Key | What it holds |
|
|
1621
|
-
| ----------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
1622
|
-
| `author.name` | What to print: their display name, else their username, else the raw name the file gives. |
|
|
1623
|
-
| `author.url` | Their archive, `/author/{username}/`. **Absent** when the name is nobody this site has. |
|
|
1624
|
-
| `author.username` | Their login. Absent for the same reason `url` is. |
|
|
1625
|
-
| `author.bio` | What they wrote about themselves, when they wrote any. |
|
|
1626
|
-
| `author.avatar` | Their picture, as a path or URL. Absent when they have none. |
|
|
1627
|
-
| `author.jobTitle` | What they do, when their profile says. |
|
|
1628
|
-
| `author.location` | Where they are, as they wrote it. |
|
|
1629
|
-
| `author.links` | `{ label, href }` for each link on their profile, in the order they listed them. Absent when they listed none. |
|
|
1630
|
-
|
|
1631
|
-
`author` is absent altogether when the document names no author, so anything
|
|
1632
|
-
printing it is `{% if author %}`. Guard the link with `{% if author.url %}`: a
|
|
1633
|
-
file may name somebody who has no account here — a guest post, or a colleague
|
|
1634
|
-
whose account has gone — and that name is still printed, it simply links
|
|
1635
|
-
nowhere. [The bio](#the-bio) does both, and is what the theme prints instead of
|
|
1636
|
-
the `partials/byline.njk` it used to have.
|
|
1637
|
-
|
|
1638
|
-
`layouts/author.njk` is that person's archive, at `/author/{username}/`, with
|
|
1639
|
-
their pages at `/author/{username}/page/2/` and their three feeds under
|
|
1640
|
-
`/author/{username}/feed/`. It is headed with their name and then the bio —
|
|
1641
|
-
the avatar, the note and the `rel="me"` links, the same card that ends each of
|
|
1642
|
-
their posts — and lists their published posts newest first. The source design has no author archive, so
|
|
1643
|
-
the heading is the CMS's own; the h-card under it is the one partial the theme
|
|
1644
|
-
has. A user with no profile still has one; they are called by their username.
|
|
1645
|
-
|
|
1646
|
-
`siteAuthor` is the same object on a different question: not who wrote this
|
|
1647
|
-
document, but who the page in front of the reader is by. It is what the bio and
|
|
1648
|
-
any structured data a theme emits should both read, so that what a reader sees
|
|
1649
|
-
and what a machine reads cannot drift apart. The footer does not read it at
|
|
1650
|
-
all: the site's links are `menus.footer`, typed rather than borrowed from an
|
|
1651
|
-
account. It resolves in this order:
|
|
1652
|
-
|
|
1653
|
-
- the document's own `author`, on a post or a page that names one;
|
|
1654
|
-
- the person whose archive it is, on an author archive;
|
|
1655
|
-
- the site author, `soloAuthor`, everywhere else — the home page, a taxonomy
|
|
1656
|
-
archive, a page that names nobody, the 404.
|
|
1657
|
-
|
|
1658
|
-
It is **absent** when none of those name anybody this site has, which on a
|
|
1659
|
-
site with several authors is every page about nobody in particular. Write
|
|
1660
|
-
`{% if siteAuthor %}` around the bio.
|
|
1661
|
-
|
|
1662
|
-
The URL is not only a page. Each user is an ActivityPub actor at that address,
|
|
1663
|
-
so it is the page a follower lands on when they click through from the
|
|
1664
|
-
fediverse. `author` is therefore a reserved first URL segment, like `tag` and
|
|
1665
|
-
`feed`: no document can be permalinked under it and no taxonomy base can take
|
|
1666
|
-
it.
|
|
1667
|
-
|
|
1668
|
-
## Navigation
|
|
1669
|
-
|
|
1670
|
-
A site stores its menus by name in `content/_data/site.json`:
|
|
1671
|
-
|
|
1672
|
-
```json
|
|
1673
|
-
{
|
|
1674
|
-
"menus": {
|
|
1675
|
-
"primary": [
|
|
1676
|
-
{ "label": "Home", "url": "/" },
|
|
1677
|
-
{ "label": "About", "url": "/about/" }
|
|
1678
|
-
],
|
|
1679
|
-
"footer": [
|
|
1680
|
-
{ "label": "Colophon", "url": "/colophon/" },
|
|
1681
|
-
{ "label": "Mastodon", "url": "https://example.social/@me", "rel": "me" }
|
|
1682
|
-
]
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
```
|
|
1686
|
-
|
|
1687
|
-
Every template gets them as `menus`, keyed by the same names, already in order
|
|
1688
|
-
and already knowing which item is the page being looked at:
|
|
1689
|
-
|
|
1690
|
-
```njk
|
|
1691
|
-
{% for item in menus.footer %}
|
|
1692
|
-
<a href="{{ item.url | url }}">{{ item.label }}</a>
|
|
1693
|
-
{% endfor %}
|
|
1694
|
-
```
|
|
1695
|
-
|
|
1696
|
-
That is the whole interface, so a theme renders a menu this one has never heard
|
|
1697
|
-
of by looping over the name a site stored it under.
|
|
1698
|
-
|
|
1699
|
-
### The areas a theme declares
|
|
1700
|
-
|
|
1701
|
-
`theme.json` says where a theme renders a menu, so the Navigation screen can
|
|
1702
|
-
offer those places and nowhere else. This theme declares two:
|
|
1703
|
-
|
|
1704
|
-
```json
|
|
1705
|
-
{
|
|
1706
|
-
"name": "Default",
|
|
1707
|
-
"kind": "site",
|
|
1708
|
-
"areas": [
|
|
1709
|
-
{ "name": "primary", "label": "Site menu" },
|
|
1710
|
-
{ "name": "footer", "label": "Footer links" }
|
|
1711
|
-
]
|
|
1712
|
-
}
|
|
1713
|
-
```
|
|
1714
|
-
|
|
1715
|
-
`name` is the key under `menus`, in `site.json` and on the context. `label` is
|
|
1716
|
-
what a person choosing an area reads; leave it out and it falls back to the
|
|
1717
|
-
name. Declare a third area and a site can fill it in; declare none and the
|
|
1718
|
-
theme renders no menu.
|
|
1719
|
-
|
|
1720
|
-
The declaration is for the screen, not for the render. Every menu the site
|
|
1721
|
-
stores is on the context whether or not a theme declared an area for it — a
|
|
1722
|
-
menu nothing loops over is rendered nowhere and kept, which is what lets
|
|
1723
|
-
somebody write the menu a theme will use before switching to that theme. An
|
|
1724
|
-
`areas` that is missing, or that will not read, leaves the theme with none
|
|
1725
|
-
rather than failing to load it.
|
|
1726
|
-
|
|
1727
|
-
### One item
|
|
1728
|
-
|
|
1729
|
-
Each item is `{ label, url, current }`, plus `rel` on a link that carries `rel`
|
|
1730
|
-
values. `url` is a site-root path or an absolute URL for somewhere else, so put
|
|
1731
|
-
it through the `url` filter and a site served from a subdirectory still links
|
|
1732
|
-
correctly. `current` is true for the item whose path is the one being rendered,
|
|
1733
|
-
comparing without the trailing slash; an item pointing off the site is never
|
|
1734
|
-
current. `rel` is one string holding every value the line carried, already
|
|
1735
|
-
lower case and each said once — print it as the whole attribute,
|
|
1736
|
-
`rel="{{ item.rel }}"`, rather than building one of your own. `me` is the value
|
|
1737
|
-
most menus have, for the IndieWeb's identity check: Mastodon verifies a link on
|
|
1738
|
-
a profile by looking for a `rel="me"` link back, so a footer link to a profile
|
|
1739
|
-
marked `me` is what makes the tick appear.
|
|
1740
|
-
|
|
1741
|
-
### Where this theme prints them
|
|
1742
|
-
|
|
1743
|
-
`partials/menu.njk` holds the markup, as one macro:
|
|
1744
|
-
|
|
1745
|
-
```njk
|
|
1746
|
-
{% import "partials/menu.njk" as nav %}
|
|
1747
|
-
{{ nav.list(menus.footer, "Footer") }}
|
|
1748
|
-
```
|
|
1749
|
-
|
|
1750
|
-
The second argument is the `aria-label`, which is what tells a reader on a
|
|
1751
|
-
screen reader which of a page's menus this one is. Nothing is printed for a
|
|
1752
|
-
menu with nothing in it.
|
|
1753
|
-
|
|
1754
|
-
`menus.primary` is printed once, in `header.global-header`, on every page
|
|
1755
|
-
(TASK-105). At the root, where the header is the site title with the tagline
|
|
1756
|
-
under it, the menu is a line of its own under the tagline; everywhere else,
|
|
1757
|
-
where the header is the small link home, the stylesheet lays the header out as
|
|
1758
|
-
one line and the menu sits beside it. Nothing decides between two places any
|
|
1759
|
-
more: a reader looks in the same spot whatever they are reading, and a layout
|
|
1760
|
-
that overrides the `content` block cannot lose the navigation by not printing
|
|
1761
|
-
a bio.
|
|
1762
|
-
|
|
1763
|
-
`menus.footer` is printed in the `footer` block of `layouts/base.njk`, under
|
|
1764
|
-
the copyright line, and is the whole of what the footer links — a feed, a
|
|
1765
|
-
colophon, a webring, a profile marked `me`. The footer reads nothing off an
|
|
1766
|
-
account: it used to print one `rel="me"` link per entry of `siteAuthor.links`,
|
|
1767
|
-
which meant one nominated user's links stood for the site and nobody else's
|
|
1768
|
-
appeared at all.
|
|
1769
|
-
|
|
1770
|
-
Every menu reads the same: a link in one is the colour, the underline and the
|
|
1771
|
-
size of a link in the page's prose, in the sans heading font a `nav` is set in
|
|
1772
|
-
(TASK-111). A menu is navigation somebody is meant to use rather than a
|
|
1773
|
-
footnote, so none of them is set smaller than the words around it, and the same
|
|
1774
|
-
goes for the `rel="me"` links `partials/bio.njk` prints under a post. The two
|
|
1775
|
-
links that read as plain text instead are the site title and
|
|
1776
|
-
`a.header-link-home`, which are the page saying where it is rather than
|
|
1777
|
-
somewhere to go, and the stylesheet names those two rather than reaching for
|
|
1778
|
-
every link that happens to sit in a `<header>`.
|
|
1779
|
-
|
|
1780
|
-
`menus.primary` is the whole of the site menu, in the order the Navigation
|
|
1781
|
-
screen names it. Both areas are declared in this theme's `theme.json`, which is
|
|
1782
|
-
what puts them on `/admin/navigation` with those labels; a theme that renders a
|
|
1783
|
-
third menu declares a third area. A page cannot put itself in a menu; a page
|
|
1784
|
-
that should be linked — including the one a site serves as its front page,
|
|
1785
|
-
typed `Home | /` — is typed into the box there.
|
|
1786
|
-
|
|
1787
|
-
Because the menus live in `site.json`, an Eleventy build of the same content
|
|
1788
|
-
renders the same ones; `docs/eleventy.config.example.js` builds them as
|
|
1789
|
-
`collections.menus`.
|
|
1790
|
-
|
|
1791
|
-
## Feeds
|
|
1792
|
-
|
|
1793
|
-
`partials/feeds.njk` holds two macros. `feedLinks(root, title)` writes the three
|
|
1794
|
-
`rel="alternate"` links for a listing's RSS, Atom and JSON feeds — RSS first,
|
|
1795
|
-
because it is what most subscribers hold:
|
|
1796
|
-
|
|
1797
|
-
```njk
|
|
1798
|
-
{% import "partials/feeds.njk" as feeds %}
|
|
1799
|
-
{{ feeds.feedLinks("/", site.title) }}
|
|
1800
|
-
```
|
|
1801
|
-
|
|
1802
|
-
`root` is any listing URL ending in `/`; the macro appends `feed/`, `feed/atom/`
|
|
1803
|
-
and `feed/json/` and puts each through the `url` filter, so the links carry the
|
|
1804
|
-
base path of a site served from a subdirectory. The arguments carry everything
|
|
1805
|
-
the macro needs, so it can be imported without `with context`.
|
|
1806
|
-
|
|
1807
|
-
`commentsFeedLink(feed, title)` writes the one link a comments feed has. It
|
|
1808
|
-
takes the feed's own URL rather than a root, because a comments feed is RSS 2.0
|
|
1809
|
-
and nothing else:
|
|
1810
|
-
|
|
1811
|
-
```njk
|
|
1812
|
-
{{ feeds.commentsFeedLink("/comments/feed/", site.title + " comments") }}
|
|
1813
|
-
```
|
|
1814
|
-
|
|
1815
|
-
`layouts/tag.njk`, `layouts/category.njk` and `layouts/author.njk` each call
|
|
1816
|
-
`feedLinks` over their own root, so an archive advertises its own three feeds
|
|
1817
|
-
as well as the site's — on an author archive that root is `author.url`.
|
|
1818
|
-
|
|
1819
|
-
`layouts/base.njk` fills the `alternates` block with `feedLinks` over `/` and
|
|
1820
|
-
then the site's comments feed, so every page that extends it advertises all
|
|
1821
|
-
four. On a published post, and on a page that takes comments, it adds that
|
|
1822
|
-
document's own comments feed, from `commentsFeed` on the context. On a
|
|
1823
|
-
published post it also adds a link pointing at the post's ActivityPub object:
|
|
1824
|
-
|
|
1825
|
-
```html
|
|
1826
|
-
<link
|
|
1827
|
-
rel="alternate"
|
|
1828
|
-
type="application/activity+json"
|
|
1829
|
-
href="https://example.com/2026/09/hello/"
|
|
1830
|
-
/>
|
|
1831
|
-
```
|
|
1832
|
-
|
|
1833
|
-
A post's object id is its permalink, so that link usually points at the page it
|
|
1834
|
-
is on: it says the URL answers ActivityStreams as well as HTML. A post migrated
|
|
1835
|
-
from elsewhere keeps the id its file names, and the link points there instead.
|
|
1836
|
-
It comes from `activityStreams`, which is only on the context of a rendered
|
|
1837
|
-
published post, as `commentsFeed` is only on a document that has a comments
|
|
1838
|
-
feed, so a layout that overrides the block and does not call `super()` has to
|
|
1839
|
-
emit both itself. They are on the context rather
|
|
1840
|
-
than in `post.njk` so that a site which overrides that layout, as the demo
|
|
1841
|
-
does, keeps them.
|
|
1842
|
-
|
|
1843
|
-
After the block, and outside it, the base layout writes the webmention
|
|
1844
|
-
endpoint:
|
|
1845
|
-
|
|
1846
|
-
```html
|
|
1847
|
-
<link rel="webmention" href="/_geekity/webmention" />
|
|
1848
|
-
```
|
|
1849
|
-
|
|
1850
|
-
It comes from `webmention` on the context, which is on a rendered document only
|
|
1851
|
-
when the site is taking webmentions, so a theme asks `{% if webmention %}` and a
|
|
1852
|
-
site that has turned them off advertises nothing. The CMS also sends the same
|
|
1853
|
-
endpoint as a `Link` header on every representation of a document, so a sender
|
|
1854
|
-
that does not parse HTML still finds it.
|
|
1855
|
-
|
|
1856
|
-
The pingback endpoint follows it, as an absolute URL because the spec asks for
|
|
1857
|
-
one:
|
|
1858
|
-
|
|
1859
|
-
```html
|
|
1860
|
-
<link rel="pingback" href="https://example.com/_geekity/pingback" />
|
|
1861
|
-
```
|
|
1862
|
-
|
|
1863
|
-
It comes from `pingback` on the context, which is there only when the site
|
|
1864
|
-
takes webmentions and the document can be answered: every post, and a page
|
|
1865
|
-
only while it takes comments. The same endpoint goes out in an `X-Pingback`
|
|
1866
|
-
header under the same rule.
|
|
1867
|
-
|
|
1868
|
-
On the front page it also writes the site's index for language models:
|
|
1869
|
-
|
|
1870
|
-
```html
|
|
1871
|
-
<link rel="describedby" type="text/markdown" href="/llms.txt" />
|
|
1872
|
-
```
|
|
1873
|
-
|
|
1874
|
-
It comes from `llmsTxt` on the context, which is absent once the site turns the
|
|
1875
|
-
file off. The CMS sends the same link as a `Link` header on the front page.
|
|
1876
|
-
|
|
1877
|
-
`layouts/tag.njk` and `layouts/category.njk` override that block, call `super()`
|
|
1878
|
-
and add the archive's own three feeds:
|
|
1879
|
-
|
|
1880
|
-
```njk
|
|
1881
|
-
{% import "partials/feeds.njk" as feeds %}
|
|
1882
|
-
{% set tagRoot = "/" + (site.tagBase or "tag") + "/" + (tag | lower | urlencode) + "/" %}
|
|
1883
|
-
{% block alternates %}
|
|
1884
|
-
{{ super() }}
|
|
1885
|
-
{{ feeds.feedLinks(tagRoot, site.title + ": " + tag) }}
|
|
1886
|
-
{% endblock %}
|
|
1887
|
-
```
|
|
1888
|
-
|
|
1889
|
-
A layout that does not extend the base one has to emit the links itself. The
|
|
1890
|
-
feeds are generated by the CMS, not by a template, so nothing here decides what
|
|
1891
|
-
goes in them.
|
|
1892
|
-
|
|
1893
|
-
Category archives get their own three the same way.
|
|
1894
|
-
|
|
1895
|
-
## The conversation
|
|
1896
|
-
|
|
1897
|
-
`conversation` is what has been said about a post: the fediverse replies, likes
|
|
1898
|
-
and boosts its inbox was sent, the fediverse posts that quoted it with its
|
|
1899
|
-
author's approval, the comments people left on the page itself, and the
|
|
1900
|
-
webmentions other pages sent it.
|
|
1901
|
-
It is on the context of a rendered post **only when there is something in it**,
|
|
1902
|
-
so a post nobody has answered renders no empty section and a layout can simply
|
|
1903
|
-
ask:
|
|
1904
|
-
|
|
1905
|
-
```njk
|
|
1906
|
-
{% if conversation %}
|
|
1907
|
-
{% include "partials/conversation.njk" %}
|
|
1908
|
-
{% endif %}
|
|
1909
|
-
```
|
|
1910
|
-
|
|
1911
|
-
That is what `layouts/post.njk` does, and `layouts/page.njk` with it.
|
|
1912
|
-
|
|
1913
|
-
A page takes part only while it takes comments: when its front matter says
|
|
1914
|
-
`comments: true` and the site has not switched comments off. A page is closed
|
|
1915
|
-
by default, as WordPress closes them, and the CMS leaves `conversation` off the
|
|
1916
|
-
context of a closed page however much has been approved against it, so a page
|
|
1917
|
-
that turns comments off shows nothing. A post is different: it keeps its thread
|
|
1918
|
-
after it closes. Webmentions to a page are received and moderated either way,
|
|
1919
|
-
exactly as webmentions to a post are, and appear once the page opens.
|
|
1920
|
-
|
|
1921
|
-
`partials/conversation.njk` is the whole
|
|
1922
|
-
section — a `div.reactions-section` of the likes, the boosts and the mentions
|
|
1923
|
-
as facepiles grouped by kind, and then the thread as
|
|
1924
|
-
`div#comments.comments-area` — and a site replaces it with a
|
|
1925
|
-
`partials/conversation.njk` of its own in the theme it wears, exactly as it
|
|
1926
|
-
replaces any other template. It defines two macros, `face(item, icon, href)`
|
|
1927
|
-
and `group(items, label, kind, icon, source)`, and a layout that wants to place
|
|
1928
|
-
the pieces itself can import them:
|
|
1929
|
-
|
|
1930
|
-
```njk
|
|
1931
|
-
{% import "partials/conversation.njk" as reactions with context %}
|
|
1932
|
-
{{ reactions.group(conversation.likes, "Likes", "p-like", "❤️") }}
|
|
1933
|
-
```
|
|
1934
|
-
|
|
1935
|
-
One reply and everything under it is `comment(reply, classes)` in
|
|
1936
|
-
`partials/comment.njk`, which the thread and a comment's own page both import.
|
|
1937
|
-
`classes` is the reply's microformat: `h-entry`, the default, under a post, and
|
|
1938
|
-
`p-comment h-cite` on a comment's page, where the comment is the one entry.
|
|
1939
|
-
|
|
1940
|
-
A reply post prints its replies inside its own `h-entry`, as `p-comment
|
|
1941
|
-
h-cite`s, so the page it answers finds them there when it is sent a salmention
|
|
1942
|
-
(TASK-320). `layouts/post.njk` includes `partials/conversation.njk` twice for
|
|
1943
|
-
one, setting `conversationPart`: `"replies"` inside the entry for the thread,
|
|
1944
|
-
and `"reactions"` below it for the facepiles, whose links are not the entry's.
|
|
1945
|
-
Left unset, the partial prints the whole section.
|
|
1946
|
-
|
|
1947
|
-
### The shape
|
|
1948
|
-
|
|
1949
|
-
The conversation is deliberately **not** spelled in ActivityPub's vocabulary.
|
|
1950
|
-
Native comments and webmentions land in the same thread, so every
|
|
1951
|
-
entry says where it came from and what it is, and nothing else about it changes
|
|
1952
|
-
with the source. A theme that never looks at `source` renders all of them
|
|
1953
|
-
correctly; one that does can style them apart. The one thing a theme does have
|
|
1954
|
-
to look at is `kind`: a `mention` is in `conversation.mentions` rather than in
|
|
1955
|
-
the thread, because it is not an answer.
|
|
1956
|
-
|
|
1957
|
-
| Key | What it holds |
|
|
1958
|
-
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
1959
|
-
| `replies` | The replies to the post, oldest first, each carrying its own `replies`. See below. |
|
|
1960
|
-
| `likes` | The likes, oldest first, in the same shape. |
|
|
1961
|
-
| `boosts` | The boosts, oldest first, in the same shape. A webmention `repost` is one of them. |
|
|
1962
|
-
| `mentions` | The pages and fediverse quotes about this post, oldest first, in the same shape. |
|
|
1963
|
-
| `rsvps` | An event's answers, each group `{ value, label, people }`, going, maybe, interested, not going, only with people in it. |
|
|
1964
|
-
| `counts.replies` | How many replies, counted through the whole thread rather than the top of it. |
|
|
1965
|
-
| `counts.likes`, `counts.boosts`, `counts.mentions` | How many of each. |
|
|
1966
|
-
| `counts.rsvps` | How many people answered an event, in every group. |
|
|
1967
|
-
| `counts.total` | All of them added up. Zero never reaches a template: there would be no `conversation` at all. |
|
|
1968
|
-
|
|
1969
|
-
Each entry — a reply, a like or a boost — is:
|
|
1970
|
-
|
|
1971
|
-
| Key | What it holds |
|
|
1972
|
-
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1973
|
-
| `id` | What it is called: a fediverse reply's note id, a reaction's activity id, a stored comment's key. It is what an answer to it names. |
|
|
1974
|
-
| `key` | Its key (decision-51): 16 lowercase hex digits, the comment's id for one stored on the site and the key of its guid for anything else. Its anchor is `#comment-{key}` and its replies feed `/replies/{key}/`. |
|
|
1975
|
-
| `guid` | What the comments feeds call it: a native comment's `/comment/{key}/` page, a stored comment's origin (else its `url`), a fediverse note's id, a reply post's object id. |
|
|
1976
|
-
| `migrated` | `true` for a comment imported from the site this one replaced (decision-51). It shows like any other, but no feed carries it and no count a feed advertises includes it. |
|
|
1977
|
-
| `source` | `"activitypub"` for a fediverse reply, `"comment"` for one left on the page, `"webmention"` for another page linking here, `"post"` for a reply post a user of the site wrote (its `url` is the post's permalink). |
|
|
1978
|
-
| `kind` | `"reply"`, `"like"`, `"boost"`, `"repost"` or `"mention"`. A `repost` comes from a webmention; a `mention` is a webmention or an approved fediverse quote. |
|
|
1979
|
-
| `author.name` | The best name available: their display name, else their handle, else their server, else their id. For a webmention, the source's `h-card` name, else its host. |
|
|
1980
|
-
| `author.handle` | `@user@host`, or `null`. Taken from the follower or actor profile the site holds, else guessed from the actor URL unless it ends in a number. `null` for a native comment. |
|
|
1981
|
-
| `author.url` | Their profile page, the website a commenter typed, or a webmention author's `u-url`. May be `null`, so guard the link. |
|
|
1982
|
-
| `author.avatar` | Their avatar, or `null`. The site knows one for a fediverse actor whose profile it holds or whose imported comment brought their icon, and for a webmention whose `h-card` carried a `u-photo`. Always a same-origin `/_geekity/avatars/…` path, never the remote URL: see [Avatars](#avatars). |
|
|
1983
|
-
| `author.actorId` | Their id, which is what identifies them however they are named. `null` for a native comment, and for an imported one whose actor the export did not name. |
|
|
1984
|
-
| `author.username` | The site user they are, or `null`: whoever wrote a reply post, or the user a comment imported from the site this one replaced names (a WordPress comment written signed in). Such a comment's `author` is that user's, as a reply post's is: their display name, avatar and author archive. |
|
|
1985
|
-
| `url` | Where it can be read: the remote note's `url` for a fediverse reply or quote, the source page for a webmention, and `/comment/{key}/`, its own page, for a native comment (see [A comment's own page](#a-comments-own-page)). |
|
|
1986
|
-
| `content` | What it says, **already sanitised**, so print it with `\| safe`. Empty for a like or a boost. |
|
|
1987
|
-
| `published` | A `Date`: when it was published, or when it arrived if it did not say. Use the `date` filter. |
|
|
1988
|
-
| `inReplyTo` | What it answers — the post's ActivityPub id, or another reply's — and `null` for a reaction. |
|
|
1989
|
-
| `status` | `"published"`. On the record for the sources that moderate. |
|
|
1990
|
-
| `replies` | The replies to this one, oldest first, nested as deep as the site has seen. One may be a placeholder: see below. |
|
|
1991
|
-
| `rsvp` | An RSVP: `{ value, label }`, its `p-rsvp` (`yes`, `no`, `maybe` or `interested`) and the words for it. Absent otherwise. A webmention reply that is an RSVP carries one, and the default theme prints it as a `data.p-rsvp` at the top of the comment's content. On an event, an RSVP is in `rsvps` rather than the thread, and so is a fediverse `Accept` (`yes`), `TentativeAccept` (`maybe`) or `Reject` (`no`) of the event. Each person is there once, with their latest answer. |
|
|
1992
|
-
|
|
1993
|
-
Four rules decide what is in the thread, and they are the CMS's rather than a
|
|
1994
|
-
theme's: a reply whose author deleted it is gone, and its own answers move up to
|
|
1995
|
-
whatever it was answering; a comment a reader may not see (waiting for a
|
|
1996
|
-
moderator, spam or deleted) that has a visible reply under it is a placeholder,
|
|
1997
|
-
and one with none is not there at all; a like is counted once per actor,
|
|
1998
|
-
so an imported like and the same actor's like
|
|
1999
|
-
delivered here are one, and it disappears when that actor undoes it; and a note answering something this post
|
|
2000
|
-
has nothing to do with is left out.
|
|
2001
|
-
|
|
2002
|
-
A fediverse reply or reaction imported from the site this one replaced is
|
|
2003
|
-
shown once, however often its server sends it again or the reply backfill
|
|
2004
|
-
reads it: the imported comment is the one in the thread, with the moderator's
|
|
2005
|
-
decision on it, and the answers to its note thread under it.
|
|
2006
|
-
|
|
2007
|
-
A placeholder in `replies` is `{ id, withheld: true, replies }`: the hidden
|
|
2008
|
-
comment's id and the replies under it, and nothing that names its author or
|
|
2009
|
-
says what it said. Test `reply.withheld` before printing an author. A pending
|
|
2010
|
-
or spam comment's placeholder sits where the comment would; a deleted one's
|
|
2011
|
-
under the post, since nothing is known of what it answered. `counts.replies`
|
|
2012
|
-
and the comments feeds count and carry the visible replies only.
|
|
2013
|
-
|
|
2014
|
-
`content` is safe to print whatever produced it, and safe for different reasons.
|
|
2015
|
-
A fediverse reply is HTML somebody else's server composed, rebuilt from an
|
|
2016
|
-
allowlist — `a`, `p`, `br`, lists, `blockquote`, `pre`, `code` and the inline
|
|
2017
|
-
emphasis tags, with every link carrying `rel="nofollow noopener noreferrer"`. A
|
|
2018
|
-
webmention's `e-content` goes through the same allowlist. A native comment is
|
|
2019
|
-
Markdown the commenter typed, rendered with raw HTML off, no images embedded,
|
|
2020
|
-
and every link carrying `rel="nofollow ugc"`. Nothing else survives any of the
|
|
2021
|
-
three routes, so a theme may print all of them directly.
|
|
2022
|
-
|
|
2023
|
-
The packaged partial draws each entry as the source design does: an
|
|
2024
|
-
`li.comment.h-entry` carrying `id="comment-{{ reply.key }}"` and a
|
|
2025
|
-
`comment-{{ reply.source }}` class, holding an `article.comment-body` whose
|
|
2026
|
-
`footer.comment-meta` is the author as a `.comment-author.vcard.p-author.h-card`
|
|
2027
|
-
(their avatar, or the first letter of their name in a `span.comment-avatar-empty`
|
|
2028
|
-
when there is none, and their link marked `rel="nofollow ugc"` unless `author.username` says the
|
|
2029
|
-
author is a user of the site, as a reply post's author is) and the permalink as a `.comment-metadata` link around a
|
|
2030
|
-
`time.dt-published`, with a `span.comment-source` saying "via webmention" or
|
|
2031
|
-
"via the fediverse" when it came that way, then the words in
|
|
2032
|
-
`div.comment-content.e-content`. Answers nest in an
|
|
2033
|
-
`ol.children` inside what they answer. A Reply link in a `div.reply` goes on the
|
|
2034
|
-
ones written here — `source == "comment"` — when the post is still open; it
|
|
2035
|
-
carries the comment's key to the form as `?reply_to=`, so threading needs no
|
|
2036
|
-
JavaScript. A fediverse reply is answered on the server that holds it and a
|
|
2037
|
-
webmention on the page that sent it, so neither gets one. A placeholder is an
|
|
2038
|
-
`li.comment.comment-placeholder` with the hidden comment's anchor, a
|
|
2039
|
-
`p.comment-withheld` printing `withheld()` ("This comment is no longer
|
|
2040
|
-
shown."), and its replies in an `ol.children`; it is no microformat of its own.
|
|
2041
|
-
|
|
2042
|
-
Above the thread, `group()` draws one `div.reaction-group` per kind that has
|
|
2043
|
-
anything — `p-like`, `p-repost` and `p-mention`, the source theme's classes — as
|
|
2044
|
-
an `h2.reaction-title` of the label and the count, in a `span.reaction-count`,
|
|
2045
|
-
beside a `div.facepile` of
|
|
2046
|
-
`a.u-url` faces. A face is a round `u-photo` avatar; a reaction whose author the
|
|
2047
|
-
site knows no picture of is the emoji badge of its kind and a `.reaction-name`
|
|
2048
|
-
instead. Likes and boosts link to the person, and a mention to the page it came
|
|
2049
|
-
from, because the page is the thing worth reading.
|
|
2050
|
-
|
|
2051
|
-
There is no key saying a post has stopped taking comments, and none is needed:
|
|
2052
|
-
**the absence of `commentForm` is what a closed post looks like**, since the CMS
|
|
2053
|
-
puts the form on the context only while the site switch, the closing window and
|
|
2054
|
-
the post's own front matter all agree it is open. The packaged partial prints
|
|
2055
|
-
`p.no-comments` under a thread when there is no form, which is WordPress's
|
|
2056
|
-
"Comments are closed." and the reason a reader cannot find one.
|
|
2057
|
-
|
|
2058
|
-
An Eleventy build of the same content gets the same thing from the same files:
|
|
2059
|
-
`docs/eleventy.config.example.js` adds a `conversation` filter over
|
|
2060
|
-
`federation.inbox`, and puts the post's ActivityPub id on the context as
|
|
2061
|
-
`activityStreams` and its slug as `geekitySlug`, so a layout reads
|
|
2062
|
-
|
|
2063
|
-
```njk
|
|
2064
|
-
{% set conversation = federation.inbox | conversation(activityStreams, geekitySlug) %}
|
|
2065
|
-
```
|
|
2066
|
-
|
|
2067
|
-
and then loops over exactly the keys above. The slug is what names the post's
|
|
2068
|
-
comment file under `content/_data/comments/`; the format is documented in
|
|
2069
|
-
backlog doc-6. Two things differ: a native comment's `url` is its
|
|
2070
|
-
`#comment-{key}` anchor, because a static build writes no comment pages, and
|
|
2071
|
-
the site's own reply posts (`source: "post"`) are not in the thread.
|
|
2072
|
-
|
|
2073
|
-
### A comment's own page
|
|
2074
|
-
|
|
2075
|
-
A comment left through the form has a page of its own at `/comment/{key}/`,
|
|
2076
|
-
which is its `url` everywhere: the thread's permalink, the comments feeds and
|
|
2077
|
-
what another site fetches when it replies to the comment. The anchor
|
|
2078
|
-
`#comment-{key}` on the post stays, so an old link still scrolls to it. A
|
|
2079
|
-
webmention and a fediverse reply have no such page: their `url` is where they
|
|
2080
|
-
came from.
|
|
2081
|
-
|
|
2082
|
-
`layouts/comment.njk` draws it, with the comment as the page's one `h-entry`.
|
|
2083
|
-
It answers only while the post shows its conversation, and is `noindex` and on
|
|
2084
|
-
no list: not the sitemap, the post feeds or search. Its context:
|
|
2085
|
-
|
|
2086
|
-
| Key | What it holds |
|
|
2087
|
-
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2088
|
-
| `comment` | The comment, an entry of the shape above, its `replies` nested at every depth by the thread's own rules. |
|
|
2089
|
-
| `ancestors` | What it answers, from the top-level comment down to its parent. An ancestor a reader may not see (waiting for a moderator, spam, deleted, withdrawn) is `null`, so the chain keeps its place. |
|
|
2090
|
-
| `post` | The post it is on, as a listing entry is: `label`, `url`, `date`, `summary`, `author` and the rest. |
|
|
2091
|
-
| `threadUrl` | The comment's anchor on the post, for a link back to the whole conversation. |
|
|
2092
|
-
|
|
2093
|
-
The packaged layout prints the chain above the comment as nested
|
|
2094
|
-
`u-in-reply-to h-cite`s: the comment cites its parent, which cites its own,
|
|
2095
|
-
down to the post, which is printed first, so a parser and a reader meet the
|
|
2096
|
-
thread in the same order. A `null` ancestor is a cite with no author and no
|
|
2097
|
-
words, printing the same `withheld()` the thread's placeholder does. The replies follow inside the
|
|
2098
|
-
entry as `comment(reply, "p-comment h-cite")`, with no Reply links, and a
|
|
2099
|
-
link to `threadUrl` ends the page.
|
|
2100
|
-
|
|
2101
|
-
### Avatars
|
|
2102
|
-
|
|
2103
|
-
A reader's browser never fetches a face from the server it lives on. The
|
|
2104
|
-
`author.avatar` a theme is handed is a path on this site, under
|
|
2105
|
-
`/_geekity/avatars/`, and the CMS fetches the picture, crops it to an
|
|
2106
|
-
80-pixel square WebP (twice the 40 pixels this theme shows a commenter at)
|
|
2107
|
-
and keeps it under `data/avatars/`. Print it as it is; it needs no filter.
|
|
2108
|
-
|
|
2109
|
-
The route serves only the pictures the site shows: a follower who has
|
|
2110
|
-
liked, boosted, quoted or answered a post, and the author of an approved
|
|
2111
|
-
comment or webmention. Any other URL is a 404, and nothing is fetched for it.
|
|
2112
|
-
Pictures are fetched in the background when the site starts and every six
|
|
2113
|
-
hours after, refreshed once they are a week old, and forgotten once nothing
|
|
2114
|
-
shows them. A picture that cannot be fetched, is over 2 MB, or is not a JPEG,
|
|
2115
|
-
PNG, GIF, WebP or AVIF is served as a grey placeholder, and tried again an
|
|
2116
|
-
hour later. `data/avatars/` is a cache: delete it and every picture is fetched
|
|
2117
|
-
again.
|
|
2118
|
-
|
|
2119
|
-
## The comment form
|
|
2120
|
-
|
|
2121
|
-
`commentForm` is on the context of a rendered post **only when that post is
|
|
2122
|
-
still taking comments**. A site switch, a closing window counted from the
|
|
2123
|
-
post's date, and `comments: true` or `comments: false` in the post's own front
|
|
2124
|
-
matter all agree before it gets here, so a layout asks:
|
|
2125
|
-
|
|
2126
|
-
```njk
|
|
2127
|
-
{% if commentForm %}
|
|
2128
|
-
{% include "partials/comment-form.njk" %}
|
|
2129
|
-
{% endif %}
|
|
2130
|
-
```
|
|
2131
|
-
|
|
2132
|
-
That is what `layouts/post.njk` does, after the conversation. A closed post
|
|
2133
|
-
still shows the thread — including every fediverse reply, which arrives whether
|
|
2134
|
-
a post is open or not — and simply has no form, which is what the conversation
|
|
2135
|
-
reads to print "Comments are closed." A site replaces
|
|
2136
|
-
`partials/comment-form.njk` the way it replaces any other template.
|
|
2137
|
-
|
|
2138
|
-
The packaged partial is a `section#respond.comment-respond` with an
|
|
2139
|
-
`h2.comment-reply-title`, one paragraph per field and the button in a
|
|
2140
|
-
`p.form-submit` — the source design's names, so the stylesheet styles every
|
|
2141
|
-
label, field and button of both forms through `.comment-respond` and a site
|
|
2142
|
-
that has CSS for the WordPress theme can bring it. The name and email fields sit
|
|
2143
|
-
in a `div.field-row`, side by side on a wide screen, in both forms. On a site
|
|
2144
|
-
that takes webmentions a `p.comment-help` under the heading says a reader can
|
|
2145
|
-
reply from their own site instead.
|
|
2146
|
-
|
|
2147
|
-
| Key | What it holds |
|
|
2148
|
-
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2149
|
-
| `action` | Where the form posts. One fixed path; the post travels as a field. |
|
|
2150
|
-
| `fields` | The name each field is submitted under: `post`, `name`, `email`, `url`, `body`, `inReplyTo`, `trap`, `loaded`, `notify`, `csrf`, `listed`. Use these rather than typing the names. |
|
|
2151
|
-
| `post` | The post's slug, for the hidden field. |
|
|
2152
|
-
| `loaded` | When this form was rendered, in epoch milliseconds, for the hidden field. A submission that comes back too fast is refused. |
|
|
2153
|
-
| `values` | What is in the fields: empty on a fresh form, what was typed on a refused one. |
|
|
2154
|
-
| `problems` | One message per field a person has to put right — `name`, `email`, `url`, `body`. |
|
|
2155
|
-
| `error` | A message about the submission as a whole, when there is one. |
|
|
2156
|
-
| `notifiable` | Whether to offer `fields.notify`, the "email me when somebody replies" box. False on a site that sends no mail, where the box would promise a message nothing could send. |
|
|
2157
|
-
| `nameLength`, `bodyLength` | The `maxlength` for the two fields that have one. |
|
|
2158
|
-
|
|
2159
|
-
`fields.trap` is a honeypot: render it, hide it from sight and from assistive
|
|
2160
|
-
technology, and give it `tabindex="-1"` and `autocomplete="off"`. A submission
|
|
2161
|
-
that filled it is dropped. **Do not** remove it from a replacement partial —
|
|
2162
|
-
it is one of three things standing between the site and a spam queue.
|
|
2163
|
-
|
|
2164
|
-
### When a submission is refused
|
|
2165
|
-
|
|
2166
|
-
The CMS answers a refused comment with the post again, `problems` and `error`
|
|
2167
|
-
filled in. The packaged partial tells assistive technology about it in two
|
|
2168
|
-
places, and a replacement partial should do the same:
|
|
2169
|
-
|
|
2170
|
-
- **A summary before the form.** One element with `role="alert"`,
|
|
2171
|
-
`tabindex="-1"` and `autofocus`, labelled by a heading that is `error`, or a
|
|
2172
|
-
sentence of your own when there is none, and holding a link to each field in
|
|
2173
|
-
`problems` (`<a href="#comment-name">…</a>`). `autofocus` moves focus to it on
|
|
2174
|
-
load with no script, so it has to be the first `autofocus` on the page.
|
|
2175
|
-
- **On each field in `problems`.** `aria-invalid="true"` and
|
|
2176
|
-
`aria-describedby` naming the element that holds its message, so a screen
|
|
2177
|
-
reader reads the message with the field.
|
|
2178
|
-
|
|
2179
|
-
The contact form below keeps the same contract.
|
|
2180
|
-
|
|
2181
|
-
### When somebody is signed in
|
|
2182
|
-
|
|
2183
|
-
Two more keys are on `commentForm` when — and only when — the request carries a
|
|
2184
|
-
valid session for this site's admin:
|
|
2185
|
-
|
|
2186
|
-
| Key | What it holds |
|
|
2187
|
-
| ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
2188
|
-
| `signedInAs` | Who the reply will be posted as: `name`, which is their display name or their username, and `url`, which is their author archive. |
|
|
2189
|
-
| `csrfToken` | The value for a hidden `fields.csrf`, without which the submission is refused. |
|
|
2190
|
-
|
|
2191
|
-
**A replacement partial has to draw both forms**, because the site draws this
|
|
2192
|
-
one for its own author and the stranger's one for everybody else:
|
|
2193
|
-
|
|
2194
|
-
```njk
|
|
2195
|
-
{% if commentForm.signedInAs %}
|
|
2196
|
-
<input type="hidden" name="{{ commentForm.fields.csrf }}" value="{{ commentForm.csrfToken }}" />
|
|
2197
|
-
<p class="comment-signed-in">
|
|
2198
|
-
Commenting as <a href="{{ commentForm.signedInAs.url }}">{{ commentForm.signedInAs.name }}</a>.
|
|
2199
|
-
</p>
|
|
2200
|
-
<p class="comment-check">
|
|
2201
|
-
<input id="comment-listed" name="{{ commentForm.fields.listed }}" type="checkbox" value="1"{% if commentForm.values.listed %} checked{% endif %} />
|
|
2202
|
-
<label for="comment-listed">Include in posts and feeds</label>
|
|
2203
|
-
</p>
|
|
2204
|
-
{% else %}
|
|
2205
|
-
{# fields.loaded, fields.trap, and the name, email and website boxes #}
|
|
2206
|
-
{% endif %}
|
|
2207
|
-
```
|
|
2208
|
-
|
|
2209
|
-
The signed-in branch renders **no** name, email or website box, **no**
|
|
2210
|
-
`fields.trap` and **no** `fields.loaded`: the site already knows all three
|
|
2211
|
-
answers, and a session is better evidence that this is a person than a honeypot
|
|
2212
|
-
or a stopwatch. It renders `fields.csrf` instead, because this is the one form
|
|
2213
|
-
on the public site that acts on somebody's behalf — without the token, a page
|
|
2214
|
-
on another site could make them post under their own name. The comment box, the
|
|
2215
|
-
reply-to field and the notify box are the same in both branches.
|
|
2216
|
-
|
|
2217
|
-
What the signed-in branch posts is a reply post rather than a comment (CMS
|
|
2218
|
-
README, "Replying while signed in"). It adds `fields.listed`, a checkbox
|
|
2219
|
-
labelled "Include in posts and feeds" and unchecked by default: unchecked
|
|
2220
|
-
publishes the reply post unlisted, checked publishes it public. The stranger's
|
|
2221
|
-
branch never draws it, and the CMS ignores it from a stranger.
|
|
2222
|
-
|
|
2223
|
-
A Reply link, `?reply_to={key}#respond`, goes on every entry of the thread for
|
|
2224
|
-
somebody signed in, and on `source == "comment"` entries only for a stranger.
|
|
2225
|
-
`partials/comment.njk` asks `reply.source == "comment" or
|
|
2226
|
-
commentForm.signedInAs`.
|
|
2227
|
-
|
|
2228
|
-
The account's email address is **never** on the context. The comment is stored
|
|
2229
|
-
with it, exactly as a stranger's is, and it is no more printable than theirs.
|
|
2230
|
-
|
|
2231
|
-
A page carrying this form is one reader's page rather than the post, so the CMS
|
|
2232
|
-
answers it `Cache-Control: private, no-store` and with no ETag. A theme does
|
|
2233
|
-
not have to do anything about that, but it is why printing somebody's name into
|
|
2234
|
-
a page is safe here.
|
|
2235
|
-
|
|
2236
|
-
Two more keys travel beside it, both from the URL rather than from the post:
|
|
2237
|
-
|
|
2238
|
-
| Key | What it holds |
|
|
2239
|
-
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
2240
|
-
| `commentNotice` | The thank-you after a submission, from the `?comment=` the redirect carried. |
|
|
2241
|
-
| `commentReplyTo`, `commentReplyingTo` | The comment a `?reply_to=` link named and the name on it, once the CMS has checked it is an approved comment on this very post. |
|
|
2242
|
-
|
|
2243
|
-
## The contact form
|
|
2244
|
-
|
|
2245
|
-
`contactForm` is on the context of a rendered document **only when its front
|
|
2246
|
-
matter says `contact: true`**, so a layout asks:
|
|
2247
|
-
|
|
2248
|
-
```njk
|
|
2249
|
-
{% if contactForm %}
|
|
2250
|
-
{% include "partials/contact-form.njk" %}
|
|
2251
|
-
{% endif %}
|
|
2252
|
-
```
|
|
2253
|
-
|
|
2254
|
-
That is what `layouts/page.njk` does, after the page's content. The key is
|
|
2255
|
-
honoured wherever it is written, so a theme that wants the form under a post as
|
|
2256
|
-
well only has to add the same two lines to `layouts/post.njk`; the editor
|
|
2257
|
-
offers the checkbox on pages.
|
|
2258
|
-
|
|
2259
|
-
`archive: true` is the other front matter key of this kind: it puts every
|
|
2260
|
-
published post on the page instead of a form. See
|
|
2261
|
-
[An archive page](#an-archive-page).
|
|
2262
|
-
|
|
2263
|
-
The packaged partial is a `section#contact.comment-respond.contact-form`: it is
|
|
2264
|
-
the same kind of form as the comment one, so it wears the same class and the two
|
|
2265
|
-
share one set of rules rather than keeping two that drift.
|
|
2266
|
-
|
|
2267
|
-
| Key | What it holds |
|
|
2268
|
-
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
2269
|
-
| `action` | Where the form posts. One fixed path; the page travels as a field. |
|
|
2270
|
-
| `fields` | The name each field is submitted under: `page`, `name`, `email`, `subject`, `message`, `trap`, `loaded`. Use these rather than typing the names. |
|
|
2271
|
-
| `page` | The page's slug, for the hidden field. |
|
|
2272
|
-
| `loaded` | When this form was rendered, in epoch milliseconds, for the hidden field. A submission that comes back too fast is refused. |
|
|
2273
|
-
| `values` | What is in the fields: empty on a fresh form, what was typed on a refused one. |
|
|
2274
|
-
| `problems` | One message per field a person has to put right — `name`, `email`, `subject`, `message`. |
|
|
2275
|
-
| `error` | A message about the submission as a whole, when there is one. |
|
|
2276
|
-
| `nameLength`, `subjectLength`, `messageLength` | The `maxlength` for the three fields that have one. |
|
|
2277
|
-
|
|
2278
|
-
`fields.trap` is the same honeypot the comment form carries, and the same rules
|
|
2279
|
-
apply: render it, hide it, and **do not** remove it from a replacement partial.
|
|
2280
|
-
|
|
2281
|
-
One more key travels beside it, from the URL rather than from the page:
|
|
2282
|
-
|
|
2283
|
-
| Key | What it holds |
|
|
2284
|
-
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2285
|
-
| `contactNotice` | The thank-you after a message was sent, from the `?contact=` the redirect carried. The shipped partial shows it **instead of** the form. |
|
|
2286
|
-
|
|
2287
|
-
**Where the message goes is never on the context.** The contact address is read
|
|
2288
|
-
when a submission arrives, so it cannot end up in the HTML however this partial
|
|
2289
|
-
is rewritten. Do not try to print it, and do not put a `mailto:` beside the
|
|
2290
|
-
form.
|
|
2291
|
-
|
|
2292
|
-
## Taxonomy macros
|
|
2293
|
-
|
|
2294
|
-
`partials/tags.njk` holds one macro per taxonomy, and each renders nothing at
|
|
2295
|
-
all for an empty list. Both are one `p.post-categories` of `p-category` links,
|
|
2296
|
-
which is the shape the design prints in a feed item's meta line and under an
|
|
2297
|
-
entry — `categories` marks each link `rel="category"` and `list` marks each
|
|
2298
|
-
`rel="tag"`, so under an entry the two read as one line of what it is filed
|
|
2299
|
-
under, categories first:
|
|
2300
|
-
|
|
2301
|
-
```njk
|
|
2302
|
-
{% import "partials/tags.njk" as taxonomy with context %}
|
|
2303
|
-
{{ taxonomy.categories(categories) }}
|
|
2304
|
-
{{ taxonomy.list(tags) }}
|
|
2305
|
-
```
|
|
2306
|
-
|
|
2307
|
-
`list` links to `/{{ site.tagBase }}/{tag}/`, the tag in lower case, and `categories` to
|
|
2308
|
-
`/{{ site.categoryBase }}/{category}/`, which is where the CMS serves each
|
|
2309
|
-
archive. Both bases are settings — `tag` and `category` until the settings
|
|
2310
|
-
screen says otherwise — and they reach a template through `site`, which is why
|
|
2311
|
-
the import above carries `with context`: a macro imported without it cannot see
|
|
2312
|
-
`site` and would fall back to the defaults.
|
|
2313
|
-
|
|
2314
|
-
## Filters
|
|
2315
|
-
|
|
2316
|
-
| Filter | What it does |
|
|
2317
|
-
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2318
|
-
| `date(format, zone, locale)` | Formats a `Date` or a date string. `long` gives `2 September 2026` on an `en` site and `2 septembre 2026` on an `fr` one; `full`, `medium` and `short` are Intl's other date styles, and `readable` (the default) is `long`. `datetime` gives `10 October 2026 at 09:00 GMT-5`, the day and the time with the zone named. `month` gives `September 2026`, `year` gives `2026`, `html` gives `2026-09-02` for a `<time datetime>`, and `iso` gives the full ISO 8601 instant. Every format but `html` and `iso` is written in the site's locale: the `locale` setting, or the `language` when that is empty. Bare `en` writes the day first, as `en-GB` does; `en-US` gives `September 2, 2026`. A date in a file is a UTC instant; every format but `iso` is rendered in the site's `timezone` setting, and `iso` stays the instant. Pass `zone` — an IANA name — to override the setting for one call. Pass `locale` — a BCP 47 tag — to write the words in another language for one call; it comes after `zone`, so pass `none` for the zone to keep the site's: `{{ date \| date("readable", none, lang) }}`. An empty locale, or one that is no tag, writes in the site's. A value that is not a date renders as the empty string. |
|
|
2319
|
-
| `plural(forms, locale)` | Picks the form for a count by its plural category in the site's locale, through `Intl.PluralRules`: `{{ n \| plural({ one: "# reply", other: "# replies" }) }}`. The keys are the CLDR categories (`zero`, `one`, `two`, `few`, `many`, `other`), a category the forms leave out falls back to `other`, and `#` is the count in the locale's digits. Pass `locale` when the strings are in a language of their own: the default theme's words are English, so it passes `"en"`. |
|
|
2320
|
-
| `url` | Prefixes a root-relative path with the base URL's path, so a site served from a subdirectory links correctly. Eleventy's filter of the same name. |
|
|
2321
|
-
| `absoluteUrl` | The same path as a fully qualified URL against the site's `baseUrl`. |
|
|
2322
|
-
| `asset` | The URL of a file in the theme's `static/` directory, with the base path in front and a hash of the file's bytes on the end: `{{ "style.css" \| asset }}` gives `/theme/style.css?v=3f2a9c01b7d4`. The file is found the way `/theme/` finds it, the chosen theme first. A hashed URL is served with `Cache-Control: public, max-age=31536000, immutable`, and editing the file changes the hash on the next render. A file no theme has gets its plain `/theme/` URL. |
|
|
2323
|
-
|
|
2324
|
-
`date` reads one word rather than parsing it: `{{ "now" | date("year") }}` is
|
|
2325
|
-
the year at the moment the page is rendered, in the site's own timezone. It is
|
|
2326
|
-
what the footer's copyright line is written with, and it is the one date a page
|
|
2327
|
-
has that no file carries.
|
|
2328
|
-
|
|
2329
|
-
The default theme passes the document's `lang` as that locale for every date
|
|
2330
|
-
inside its `<article>`: the Published and Updated lines, the hidden heading of
|
|
2331
|
-
a note, the kicker in a listing or a search result (`kicker.line(…,
|
|
2332
|
-
lang=post.lang)`), and the date of the post a reply answers. So a post that
|
|
2333
|
-
says `lang: fr` on an `en` site reads `Published 2 septembre 2026`, while the
|
|
2334
|
-
navigation, the conversation under it, the archive's month headings and the
|
|
2335
|
-
footer stay in the site's locale. A theme that wants the same passes `lang`
|
|
2336
|
-
on an entry and `post.lang` in a listing; one that passes nothing writes every
|
|
2337
|
-
date in the site's locale, as before.
|
|
2338
|
-
|
|
2339
|
-
Nunjucks' own filters — `default`, `join`, `urlencode`, `safe` and the rest —
|
|
2340
|
-
are all available. Autoescaping is on, so rendered Markdown is the one thing
|
|
2341
|
-
that has to be passed through `safe`.
|
|
3
|
+
The templates and the stylesheet that every Geekity CMS site gets, shipped
|
|
4
|
+
inside `@geekity/cms`. A site theme can replace one template at a time. The
|
|
5
|
+
rest come from this theme.
|
|
6
|
+
|
|
7
|
+
## Override a template
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
mkdir -p themes/mine/layouts
|
|
11
|
+
echo '{ "name": "Mine", "kind": "site" }' > themes/mine/theme.json
|
|
12
|
+
cp node_modules/@geekity/cms/themes/default/layouts/post.njk themes/mine/layouts/
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Edit the copy, then choose **Mine** on **Appearance > Themes** in the admin.
|
|
16
|
+
Every template that your theme does not ship still comes from the default
|
|
17
|
+
theme.
|
|
18
|
+
|
|
19
|
+
## Documentation
|
|
20
|
+
|
|
21
|
+
[The default theme](https://github.com/geekitycom/cms/blob/main/docs/theme/README.md)
|
|
22
|
+
lists every file in this folder. Each topic has a page of its own:
|
|
23
|
+
|
|
24
|
+
- [The manifest](https://github.com/geekitycom/cms/blob/main/docs/theme/manifest.md)
|
|
25
|
+
- [Overriding a template](https://github.com/geekitycom/cms/blob/main/docs/theme/overriding-a-template.md)
|
|
26
|
+
- [Adding to every page](https://github.com/geekitycom/cms/blob/main/docs/theme/adding-to-every-page.md)
|
|
27
|
+
- [The page shell](https://github.com/geekitycom/cms/blob/main/docs/theme/page-shell.md)
|
|
28
|
+
- [Mail templates](https://github.com/geekitycom/cms/blob/main/docs/theme/mail-templates.md)
|
|
29
|
+
- [The Markdown representation](https://github.com/geekitycom/cms/blob/main/docs/theme/markdown.md)
|
|
30
|
+
- [Template context](https://github.com/geekitycom/cms/blob/main/docs/theme/template-context.md)
|
|
31
|
+
- [Bylines and author archives](https://github.com/geekitycom/cms/blob/main/docs/theme/bylines.md)
|
|
32
|
+
- [Navigation](https://github.com/geekitycom/cms/blob/main/docs/theme/navigation.md)
|
|
33
|
+
- [Feeds](https://github.com/geekitycom/cms/blob/main/docs/theme/feeds.md)
|
|
34
|
+
- [The conversation](https://github.com/geekitycom/cms/blob/main/docs/theme/conversation.md)
|
|
35
|
+
- [The comment form](https://github.com/geekitycom/cms/blob/main/docs/theme/comment-form.md)
|
|
36
|
+
- [The contact form](https://github.com/geekitycom/cms/blob/main/docs/theme/contact-form.md)
|
|
37
|
+
- [Taxonomy macros](https://github.com/geekitycom/cms/blob/main/docs/theme/taxonomy-macros.md)
|
|
38
|
+
- [Filters](https://github.com/geekitycom/cms/blob/main/docs/theme/filters.md)
|