@geekity/cms 0.27.0 → 0.29.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 +469 -84
- package/admin/layouts/shell.njk +6 -0
- package/admin/pages/comments/all.njk +3 -1
- package/admin/pages/documents/editor.njk +4 -0
- package/admin/pages/federation/followers.njk +8 -2
- package/admin/pages/plugins/installed.njk +1 -1
- package/admin/pages/tools/dev-mode.njk +47 -0
- package/admin/static/admin.css +8 -0
- package/admin/static/editor-actions.js +13 -9
- package/admin/static/editor.js +31 -17
- package/dist/admin/comments.d.ts +2 -0
- package/dist/admin/comments.d.ts.map +1 -1
- package/dist/admin/comments.js +19 -49
- package/dist/admin/comments.js.map +1 -1
- package/dist/admin/dev-mode.d.ts +9 -0
- package/dist/admin/dev-mode.d.ts.map +1 -0
- package/dist/admin/dev-mode.js +42 -0
- package/dist/admin/dev-mode.js.map +1 -0
- package/dist/admin/documents.d.ts +5 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +45 -17
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/editor-actions.js +3 -1
- package/dist/admin/editor-actions.js.map +1 -1
- package/dist/admin/federation.d.ts +21 -1
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +19 -7
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/menu.d.ts.map +1 -1
- package/dist/admin/menu.js +2 -0
- package/dist/admin/menu.js.map +1 -1
- package/dist/admin/own-paragraph.d.ts +7 -0
- package/dist/admin/own-paragraph.d.ts.map +1 -0
- package/dist/admin/own-paragraph.js +9 -0
- package/dist/admin/own-paragraph.js.map +1 -0
- package/dist/admin/plugins.d.ts.map +1 -1
- package/dist/admin/plugins.js +19 -8
- package/dist/admin/plugins.js.map +1 -1
- package/dist/admin/preview.d.ts.map +1 -1
- package/dist/admin/preview.js +4 -1
- package/dist/admin/preview.js.map +1 -1
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +4 -0
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/settings-email.d.ts.map +1 -1
- package/dist/admin/settings-email.js +4 -0
- package/dist/admin/settings-email.js.map +1 -1
- package/dist/admin/store.d.ts +68 -12
- package/dist/admin/store.d.ts.map +1 -1
- package/dist/admin/store.js +167 -18
- package/dist/admin/store.js.map +1 -1
- package/dist/admin/templates.d.ts +1 -0
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +1 -0
- package/dist/admin/templates.js.map +1 -1
- package/dist/admin/uploads.d.ts +7 -1
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +33 -30
- package/dist/admin/uploads.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +78 -12
- package/dist/cli.js.map +1 -1
- package/dist/comments/form.d.ts +3 -1
- package/dist/comments/form.d.ts.map +1 -1
- package/dist/comments/form.js +2 -1
- package/dist/comments/form.js.map +1 -1
- package/dist/comments/index.d.ts +2 -2
- package/dist/comments/index.d.ts.map +1 -1
- package/dist/comments/index.js +1 -1
- package/dist/comments/index.js.map +1 -1
- package/dist/comments/records.d.ts +25 -20
- package/dist/comments/records.d.ts.map +1 -1
- package/dist/comments/records.js +113 -33
- package/dist/comments/records.js.map +1 -1
- package/dist/comments/reply-notices.d.ts +42 -0
- package/dist/comments/reply-notices.d.ts.map +1 -0
- package/dist/comments/reply-notices.js +76 -0
- package/dist/comments/reply-notices.js.map +1 -0
- package/dist/comments/reply-post.d.ts +49 -0
- package/dist/comments/reply-post.d.ts.map +1 -0
- package/dist/comments/reply-post.js +66 -0
- package/dist/comments/reply-post.js.map +1 -0
- package/dist/comments/routes.d.ts +6 -1
- package/dist/comments/routes.d.ts.map +1 -1
- package/dist/comments/routes.js +49 -21
- package/dist/comments/routes.js.map +1 -1
- package/dist/comments/submission.d.ts +8 -25
- package/dist/comments/submission.d.ts.map +1 -1
- package/dist/comments/submission.js +19 -38
- package/dist/comments/submission.js.map +1 -1
- package/dist/comments/viewer.d.ts.map +1 -1
- package/dist/comments/viewer.js +1 -0
- package/dist/comments/viewer.js.map +1 -1
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/content/citation.d.ts +1 -0
- package/dist/content/citation.d.ts.map +1 -1
- package/dist/content/citation.js +2 -0
- package/dist/content/citation.js.map +1 -1
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +33 -1
- package/dist/content/markdown.js.map +1 -1
- package/dist/content/migrated.d.ts +6 -0
- package/dist/content/migrated.d.ts.map +1 -0
- package/dist/content/migrated.js +12 -0
- package/dist/content/migrated.js.map +1 -0
- package/dist/content/original.d.ts +8 -0
- package/dist/content/original.d.ts.map +1 -0
- package/dist/content/original.js +15 -0
- package/dist/content/original.js.map +1 -0
- package/dist/content/parser.d.ts +5 -0
- package/dist/content/parser.d.ts.map +1 -1
- package/dist/content/parser.js +1 -1
- package/dist/content/parser.js.map +1 -1
- package/dist/content/schedule.d.ts +7 -0
- package/dist/content/schedule.d.ts.map +1 -1
- package/dist/content/schedule.js +11 -2
- package/dist/content/schedule.js.map +1 -1
- package/dist/content/store.d.ts +43 -11
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +170 -10
- package/dist/content/store.js.map +1 -1
- package/dist/content/sync.d.ts +8 -0
- package/dist/content/sync.d.ts.map +1 -1
- package/dist/content/sync.js +172 -57
- package/dist/content/sync.js.map +1 -1
- package/dist/content/video.d.ts +12 -0
- package/dist/content/video.d.ts.map +1 -0
- package/dist/content/video.js +91 -0
- package/dist/content/video.js.map +1 -0
- package/dist/dev-mode.d.ts +28 -0
- package/dist/dev-mode.d.ts.map +1 -0
- package/dist/dev-mode.js +64 -0
- package/dist/dev-mode.js.map +1 -0
- package/dist/drain.d.ts +9 -3
- package/dist/drain.d.ts.map +1 -1
- package/dist/drain.js +24 -3
- package/dist/drain.js.map +1 -1
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +2 -1
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/backfill.d.ts +114 -0
- package/dist/federation/backfill.d.ts.map +1 -0
- package/dist/federation/backfill.js +312 -0
- package/dist/federation/backfill.js.map +1 -0
- package/dist/federation/delivery.d.ts +2 -0
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +46 -16
- package/dist/federation/delivery.js.map +1 -1
- package/dist/federation/inbox.d.ts.map +1 -1
- package/dist/federation/inbox.js +14 -4
- package/dist/federation/inbox.js.map +1 -1
- package/dist/federation/mount.d.ts.map +1 -1
- package/dist/federation/mount.js +8 -4
- package/dist/federation/mount.js.map +1 -1
- package/dist/federation/records.d.ts +13 -0
- package/dist/federation/records.d.ts.map +1 -1
- package/dist/federation/records.js +49 -10
- package/dist/federation/records.js.map +1 -1
- package/dist/federation/relays.d.ts.map +1 -1
- package/dist/federation/relays.js +16 -1
- package/dist/federation/relays.js.map +1 -1
- package/dist/federation/replies.d.ts +6 -0
- package/dist/federation/replies.d.ts.map +1 -1
- package/dist/federation/replies.js +18 -1
- package/dist/federation/replies.js.map +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -14
- package/dist/index.js.map +1 -1
- package/dist/indexnow.d.ts +1 -1
- package/dist/indexnow.d.ts.map +1 -1
- package/dist/indexnow.js +7 -0
- package/dist/indexnow.js.map +1 -1
- package/dist/init.d.ts +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +1 -7
- package/dist/init.js.map +1 -1
- package/dist/mail/service.d.ts +3 -1
- package/dist/mail/service.d.ts.map +1 -1
- package/dist/mail/service.js +17 -0
- package/dist/mail/service.js.map +1 -1
- package/dist/notify.d.ts +1 -1
- package/dist/notify.d.ts.map +1 -1
- package/dist/notify.js +7 -0
- package/dist/notify.js.map +1 -1
- package/dist/plugin.d.ts +67 -4
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/plugins/folder.d.ts +2 -0
- package/dist/plugins/folder.d.ts.map +1 -1
- package/dist/plugins/folder.js +26 -5
- package/dist/plugins/folder.js.map +1 -1
- package/dist/plugins/manage.d.ts +8 -6
- package/dist/plugins/manage.d.ts.map +1 -1
- package/dist/plugins/manage.js +1 -4
- package/dist/plugins/manage.js.map +1 -1
- package/dist/plugins/site.d.ts.map +1 -1
- package/dist/plugins/site.js +56 -0
- package/dist/plugins/site.js.map +1 -1
- package/dist/supervisor/control.d.ts +15 -0
- package/dist/supervisor/control.d.ts.map +1 -0
- package/dist/supervisor/control.js +89 -0
- package/dist/supervisor/control.js.map +1 -0
- package/dist/supervisor/primary.d.ts +18 -4
- package/dist/supervisor/primary.d.ts.map +1 -1
- package/dist/supervisor/primary.js +128 -16
- package/dist/supervisor/primary.js.map +1 -1
- package/dist/supervisor/protocol.d.ts +12 -0
- package/dist/supervisor/protocol.d.ts.map +1 -1
- package/dist/supervisor/protocol.js +21 -2
- package/dist/supervisor/protocol.js.map +1 -1
- package/dist/supervisor/supervision.d.ts +1 -0
- package/dist/supervisor/supervision.d.ts.map +1 -1
- package/dist/supervisor/supervision.js +14 -1
- package/dist/supervisor/supervision.js.map +1 -1
- package/dist/supervisor/worker.d.ts.map +1 -1
- package/dist/supervisor/worker.js +31 -6
- package/dist/supervisor/worker.js.map +1 -1
- package/dist/web/context.d.ts +22 -0
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +20 -4
- package/dist/web/context.js.map +1 -1
- package/dist/web/conversation.d.ts +155 -16
- package/dist/web/conversation.d.ts.map +1 -1
- package/dist/web/conversation.js +602 -135
- package/dist/web/conversation.js.map +1 -1
- package/dist/web/documents.d.ts +0 -22
- package/dist/web/documents.d.ts.map +1 -1
- package/dist/web/documents.js +1 -34
- package/dist/web/documents.js.map +1 -1
- package/dist/web/feed-item.d.ts +19 -14
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +10 -9
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-rss.d.ts +2 -2
- package/dist/web/feed-rss.d.ts.map +1 -1
- package/dist/web/feed-rss.js +36 -12
- package/dist/web/feed-rss.js.map +1 -1
- package/dist/web/feed-source.d.ts +30 -1
- package/dist/web/feed-source.d.ts.map +1 -1
- package/dist/web/feed-source.js +25 -0
- package/dist/web/feed-source.js.map +1 -1
- package/dist/web/feeds.d.ts +1 -1
- package/dist/web/feeds.d.ts.map +1 -1
- package/dist/web/feeds.js +7 -2
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/guids.d.ts +59 -0
- package/dist/web/guids.d.ts.map +1 -0
- package/dist/web/guids.js +79 -0
- package/dist/web/guids.js.map +1 -0
- package/dist/web/index.d.ts +5 -6
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +3 -3
- package/dist/web/index.js.map +1 -1
- package/dist/web/redirects.d.ts +29 -10
- package/dist/web/redirects.d.ts.map +1 -1
- package/dist/web/redirects.js +99 -53
- package/dist/web/redirects.js.map +1 -1
- package/dist/web/render.d.ts +19 -9
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +44 -42
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts +2 -2
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +94 -31
- package/dist/web/routes.js.map +1 -1
- package/dist/webmention/index.d.ts +1 -1
- package/dist/webmention/index.d.ts.map +1 -1
- package/dist/webmention/index.js +1 -1
- package/dist/webmention/index.js.map +1 -1
- package/dist/webmention/links.d.ts +55 -0
- package/dist/webmention/links.d.ts.map +1 -1
- package/dist/webmention/links.js +162 -0
- package/dist/webmention/links.js.map +1 -1
- package/dist/webmention/microformats.d.ts +15 -0
- package/dist/webmention/microformats.d.ts.map +1 -1
- package/dist/webmention/microformats.js +63 -1
- package/dist/webmention/microformats.js.map +1 -1
- package/dist/webmention/pingback.js +2 -1
- package/dist/webmention/pingback.js.map +1 -1
- package/dist/webmention/receive.d.ts +5 -0
- package/dist/webmention/receive.d.ts.map +1 -1
- package/dist/webmention/receive.js +99 -3
- package/dist/webmention/receive.js.map +1 -1
- package/dist/webmention/reply-context.d.ts +15 -0
- package/dist/webmention/reply-context.d.ts.map +1 -1
- package/dist/webmention/reply-context.js +29 -0
- package/dist/webmention/reply-context.js.map +1 -1
- package/dist/webmention/reply-contexts.d.ts +6 -0
- package/dist/webmention/reply-contexts.d.ts.map +1 -1
- package/dist/webmention/reply-contexts.js +16 -10
- package/dist/webmention/reply-contexts.js.map +1 -1
- package/dist/webmention/routes.d.ts.map +1 -1
- package/dist/webmention/routes.js +2 -1
- package/dist/webmention/routes.js.map +1 -1
- package/dist/webmention/service.d.ts +31 -2
- package/dist/webmention/service.d.ts.map +1 -1
- package/dist/webmention/service.js +183 -33
- package/dist/webmention/service.js.map +1 -1
- package/package.json +1 -2
- package/themes/default/README.md +390 -75
- package/themes/default/layouts/base.njk +19 -2
- package/themes/default/layouts/comment.njk +90 -0
- package/themes/default/layouts/front-page.njk +9 -47
- package/themes/default/layouts/page.njk +2 -0
- package/themes/default/layouts/post.njk +22 -4
- package/themes/default/partials/backlinks.njk +19 -0
- package/themes/default/partials/body-end.njk +1 -0
- package/themes/default/partials/cited-page.njk +3 -1
- package/themes/default/partials/comment-form.njk +10 -0
- package/themes/default/partials/comment.njk +79 -0
- package/themes/default/partials/conversation.njk +14 -59
- package/themes/default/partials/head-end.njk +1 -0
- package/themes/default/src/style.css +46 -0
- package/themes/default/static/style.css +75 -0
package/README.md
CHANGED
|
@@ -86,26 +86,27 @@ message on a site whose `site.json` has not set a period.
|
|
|
86
86
|
site (or `npx geekity`, or a `package.json` script, which is how the generated
|
|
87
87
|
`sync` script calls it).
|
|
88
88
|
|
|
89
|
-
| Command | What it does
|
|
90
|
-
| ----------------------------------------- |
|
|
91
|
-
| `geekity serve` | Boot from the config file and listen, in a worker the command supervises:
|
|
92
|
-
| `geekity init <dir>` | Create a new site in `<dir>`. Refuses a directory that is not empty.
|
|
93
|
-
| `geekity sync` | Rebuild the content index once and exit. Exits non-zero if any file could not be parsed.
|
|
94
|
-
| `geekity rebuild` | Delete `data/geekity.db` and build it again from the files.
|
|
95
|
-
| `geekity resend --all`, `<slug>...` | Send announced posts to every follower and relay again, as they now read. See [Quote posts](#quote-posts).
|
|
96
|
-
| `geekity maintenance on`, `off`, `status` | Take the public site down on purpose with a 503 and `Retry-After`, or bring it back, without a restart. `on --until <time>` names when it should be back.
|
|
97
|
-
| `geekity
|
|
98
|
-
| `geekity
|
|
99
|
-
| `geekity
|
|
100
|
-
| `geekity
|
|
101
|
-
| `geekity --
|
|
89
|
+
| Command | What it does |
|
|
90
|
+
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `geekity serve` | Boot from the config file and listen, in a worker the command supervises: a change to the plugins folder replaces it, and a crash respawns it. The default when no command is given. |
|
|
92
|
+
| `geekity init <dir>` | Create a new site in `<dir>`. Refuses a directory that is not empty. |
|
|
93
|
+
| `geekity sync` | Rebuild the content index once and exit. Exits non-zero if any file could not be parsed. |
|
|
94
|
+
| `geekity rebuild` | Delete `data/geekity.db` and build it again from the files. |
|
|
95
|
+
| `geekity resend --all`, `<slug>...` | Send announced posts to every follower and relay again, as they now read. See [Quote posts](#quote-posts). |
|
|
96
|
+
| `geekity maintenance on`, `off`, `status` | Take the public site down on purpose with a 503 and `Retry-After`, or bring it back, without a restart. `on --until <time>` names when it should be back. |
|
|
97
|
+
| `geekity dev-mode on`, `off`, `status` | Hold everything the site would send to the outside world while it is built or migrated, or take it live. See [Dev mode](#dev-mode). |
|
|
98
|
+
| `geekity strip-metadata` | Remove location and camera metadata from files already in `content/uploads`. See [The media library](#the-media-library). |
|
|
99
|
+
| `geekity user add <name>` | Create an admin account, so a site can get its first login without the setup screen. |
|
|
100
|
+
| `geekity plugin add <package>`, `remove` | Install a plugin package's bundle from the npm registry into the plugins folder (`GEEKITY_PLUGINS_DIR`), or delete a disabled one. A running `geekity serve` reloads with the change. |
|
|
101
|
+
| `geekity --help`, `-h` | The same table, on the terminal. |
|
|
102
|
+
| `geekity --version` | The installed version. |
|
|
102
103
|
|
|
103
104
|
An installed plugin may add commands of its own, which `geekity --help` lists
|
|
104
105
|
under _Plugin commands_ and which run whether or not the plugin is enabled.
|
|
105
106
|
That includes a plugin in the plugins folder. The repository README's
|
|
106
107
|
_Plugins_ section describes `geekity plugin add` and writing a plugin.
|
|
107
108
|
|
|
108
|
-
`serve`, `sync`, `rebuild`, `resend`, `maintenance`, `strip-metadata`, `user add`, `plugin` and every plugin command take
|
|
109
|
+
`serve`, `sync`, `rebuild`, `resend`, `maintenance`, `dev-mode`, `strip-metadata`, `user add`, `plugin` and every plugin command take
|
|
109
110
|
`--config <file>`; without it they look for `geekity.config.ts`, then
|
|
110
111
|
`geekity.config.js`, then `geekity.config.mjs` in the working directory, and run
|
|
111
112
|
on defaults if there is none.
|
|
@@ -289,6 +290,7 @@ directory; absolute ones are used as given.
|
|
|
289
290
|
| `accessLogAddress` | `false` | `GEEKITY_ACCESS_LOG_ADDRESS` | Put the client address on the end of each access-log line. `trustProxy` decides which address that is. |
|
|
290
291
|
| `accessLogWriter` | stdout | — | Where the lines go instead. See [The access log](#the-access-log). |
|
|
291
292
|
| `maintenance` | `false` | `GEEKITY_MAINTENANCE` | Keep the site in maintenance mode, answering 503, until a restart without it. |
|
|
293
|
+
| `devMode` | `false` | `GEEKITY_DEV_MODE` | Start the site in dev mode, which holds every outbound side effect until `geekity dev-mode off`. See [Dev mode](#dev-mode). |
|
|
292
294
|
| `compression` | `true` | `GEEKITY_COMPRESSION` | Compress text responses with brotli or gzip. Turn it off behind a proxy that compresses. See [Compression](#compression). |
|
|
293
295
|
| `securityHeaders` | [see below](#security-headers) | — | Headers every response carries. A string replaces a default or adds a header, `false` removes one. |
|
|
294
296
|
| `onDocumentChange` | none | — | Hook run for every change to the index. See [Hooks](#hooks). |
|
|
@@ -426,7 +428,7 @@ something a site is told it may lose:
|
|
|
426
428
|
| `ap_deliveries` | Nothing. The federation screen shows its posts with "Nothing recorded." |
|
|
427
429
|
| `ap_relays` | The relay list in `site.json`: boot sends each of them a fresh `Follow`. |
|
|
428
430
|
| `password_resets`, `spent_tokens` | Nothing. Every reset link and every one-click moderation link stops working. |
|
|
429
|
-
| `cms_state` | Nothing.
|
|
431
|
+
| `cms_state` | Nothing. The scheduler's watermark and its first run. |
|
|
430
432
|
| `migrations`, `admin_migrations` | The package. They record which schema versions have run. |
|
|
431
433
|
|
|
432
434
|
So four things are actually lost:
|
|
@@ -681,6 +683,50 @@ database.
|
|
|
681
683
|
|
|
682
684
|
[FEP-044f]: https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md
|
|
683
685
|
|
|
686
|
+
### Replies to fediverse replies
|
|
687
|
+
|
|
688
|
+
Somebody who answers a fediverse reply on one of the site's posts addresses
|
|
689
|
+
the person they answer, not the site, so their reply never reaches the inbox.
|
|
690
|
+
Most servers list a note's replies in its `replies` collection, and the site
|
|
691
|
+
reads those collections to bring such replies in. The sweep starts when
|
|
692
|
+
the site serves and runs every hour, within these limits:
|
|
693
|
+
|
|
694
|
+
| Limit | Value | Meaning |
|
|
695
|
+
| -------- | -------- | ----------------------------------------------------------------------------------------------------- |
|
|
696
|
+
| Interval | 6 hours | How long a thread is left alone after it was read. |
|
|
697
|
+
| Age | 30 days | How old a post gets before its thread is no longer read, by the post's date. |
|
|
698
|
+
| Depth | 4 levels | How far below the post a fediverse reply may sit and still have its collection read. |
|
|
699
|
+
| Pages | 3 | How many pages of one collection are read. Mastodon's first page holds the author's own replies only. |
|
|
700
|
+
|
|
701
|
+
Every request is signed as the site's first account, as the actor profile
|
|
702
|
+
fetches are. A reply is kept only when it answers the note whose collection
|
|
703
|
+
listed it, is addressed to the public, and is written by an actor on the
|
|
704
|
+
note's own server. A note on the site's own origin is never fetched, because
|
|
705
|
+
the site already knows what it published.
|
|
706
|
+
|
|
707
|
+
A reply found this way is logged as the `Create` nobody delivered: a line in
|
|
708
|
+
`content/_data/federation/inbox/{yyyy}-{mm}.jsonl` with `"fetched": true` in
|
|
709
|
+
front of it, an `actor` taken from the note's `attributedTo` and the id
|
|
710
|
+
`{note id}#fetched`. From there it is a reply like any other. It is threaded
|
|
711
|
+
under the reply it answers with its own URL, counted, given a `/replies/` feed
|
|
712
|
+
and listed on the federation screen, and its author's `Delete` takes it back.
|
|
713
|
+
A note held once is never logged twice. The sweep skips a note the inbox
|
|
714
|
+
already holds, and a delivery of a note the sweep fetched first replaces the
|
|
715
|
+
fetched line.
|
|
716
|
+
|
|
717
|
+
The site removes a fetched reply, with the fetched replies under it, when its
|
|
718
|
+
server answers 404 or 410 for it, or when a collection read to its end no
|
|
719
|
+
longer lists it. A delivered reply is never removed this way. A note with no
|
|
720
|
+
`replies` collection, a server that answers with any other error, and a
|
|
721
|
+
collection cut off by the page limit change nothing. The thread keeps what it
|
|
722
|
+
holds, and the sweep goes on to the next reply.
|
|
723
|
+
|
|
724
|
+
The site reads `replies` and not the conversation a note names in `context`
|
|
725
|
+
([FEP-7888]), because Mastodon publishes `replies` and few servers publish a
|
|
726
|
+
`context` collection yet.
|
|
727
|
+
|
|
728
|
+
[FEP-7888]: https://codeberg.org/fediverse/fep/src/branch/main/fep/7888/fep-7888.md
|
|
729
|
+
|
|
684
730
|
### Relays
|
|
685
731
|
|
|
686
732
|
A [Mastodon-style relay][fepae0c] boosts every public activity it is sent on to
|
|
@@ -790,9 +836,9 @@ ignored.
|
|
|
790
836
|
|
|
791
837
|
A site that moved here from the WordPress ActivityPub plugin keeps its people's
|
|
792
838
|
actor ids and its posts' object ids, which are identity and are the CMS's own
|
|
793
|
-
(above). What it needs
|
|
839
|
+
(above). What it needs besides is its content, the plugin's old paths, which
|
|
794
840
|
followers' servers keep delivering to until they refetch the actor, and an
|
|
795
|
-
import that brings each person's key pair and followers across.
|
|
841
|
+
import that brings each person's key pair and followers across. All three are the
|
|
796
842
|
[`@geekity/plugin-wordpress`](../plugin-wordpress/README.md) package, which a
|
|
797
843
|
site born on the CMS never installs. decision-14's switch is that plugin now:
|
|
798
844
|
where the cutover used to say "turn the switch on", it says install and enable
|
|
@@ -800,27 +846,43 @@ the WordPress plugin.
|
|
|
800
846
|
|
|
801
847
|
#### The cutover, end to end
|
|
802
848
|
|
|
803
|
-
1. **
|
|
849
|
+
1. **Start the new site in dev mode.** Set `GEEKITY_DEV_MODE=true` before
|
|
850
|
+
its first boot (see [Dev mode](#dev-mode)). Followers, relays, links and
|
|
851
|
+
mail are all real from here on, and dev mode keeps every one of them from
|
|
852
|
+
hearing about the import, the edits or a scheduled post until step 4.
|
|
853
|
+
2. **Export, while the WordPress site is still up.** Each person's key pair,
|
|
804
854
|
and the followers if the old site is going away before the import runs. The
|
|
805
855
|
plugin's README says how. Note the actor id and the numeric actor id off
|
|
806
|
-
`https://example.com/?author=2`,
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
856
|
+
`https://example.com/?author=2`, take a WXR export under Tools > Export,
|
|
857
|
+
and copy `wp-content/uploads`, which the export does not carry.
|
|
858
|
+
3. **Install the WordPress plugin and import.** Add `@geekity/plugin-wordpress`
|
|
859
|
+
to the site's dependencies and to `plugins` in its config, then run
|
|
860
|
+
`geekity import wordpress example.WordPress.xml --uploads wp-content/uploads`
|
|
861
|
+
for the content and media, and
|
|
862
|
+
`geekity import wordpress-actor ada --actor-id … --wordpress-id … --keypair ada.keypair.json`
|
|
863
|
+
for each person. Both work before the plugin is enabled. Check the reports:
|
|
864
|
+
every post should be written, every conflict or clash resolved, every
|
|
865
|
+
follower added, and any follower that was skipped re-run once its server
|
|
866
|
+
answers. Tools > Dev mode shows that the imports sent nothing. Both imports
|
|
867
|
+
are safe to run again: run them over a fresh export just before step 4 to
|
|
868
|
+
pick up what WordPress received in the meantime.
|
|
869
|
+
4. **Enable the WordPress plugin, move the DNS and go live.** Enable it under
|
|
870
|
+
`/admin/plugins`, then move the DNS. Remove `GEEKITY_DEV_MODE` and run
|
|
871
|
+
`geekity dev-mode off`: nothing held during the migration is sent.
|
|
872
|
+
Followers' servers go on delivering to the plugin's old inbox paths until
|
|
873
|
+
they next refetch the actor, and the plugin is what catches those
|
|
874
|
+
deliveries. Once WordPress no longer answers, run both imports one last time
|
|
875
|
+
over its final export: they merge what arrived between the export and the
|
|
876
|
+
DNS move, and leave alone whatever this site wrote or edited since.
|
|
877
|
+
5. **Watch.** `/admin/federation` lists the users with their actor ids and
|
|
818
878
|
followers; the plugin's own screen under Plugins lists each old path with
|
|
819
879
|
the instant it was last asked for. Deliveries should thin out as each
|
|
820
880
|
follower's server refetches the actor and learns the new endpoints.
|
|
821
|
-
|
|
881
|
+
6. **Disable it.** Once every path says _Never_ again for long enough — weeks
|
|
822
882
|
rather than days, since a quiet instance refetches rarely — disable the
|
|
823
|
-
plugin.
|
|
883
|
+
plugin. Disabling it also ends the redirects from the old
|
|
884
|
+
`/wp-content/uploads/` media URLs, so a site that still gets links to
|
|
885
|
+
those keeps it enabled. The paths go away on the very next request, with nothing restarted,
|
|
824
886
|
and if something was still using them you can enable it again just as
|
|
825
887
|
quickly.
|
|
826
888
|
|
|
@@ -918,13 +980,95 @@ half is sent by a peer built in the script rather than by `fedify inbox`.
|
|
|
918
980
|
[Fedify]: https://fedify.dev/
|
|
919
981
|
[`fedify tunnel`]: https://fedify.dev/cli
|
|
920
982
|
|
|
983
|
+
## Dev mode
|
|
984
|
+
|
|
985
|
+
Building or migrating a site means running it with production data: the real
|
|
986
|
+
base URL, the real actor and keys, real followers, real links. Dev mode keeps
|
|
987
|
+
that site quiet until you take it live (decision-35). While it is on, nothing
|
|
988
|
+
the site does reaches the outside world:
|
|
989
|
+
|
|
990
|
+
- No ActivityPub delivery to followers or relays, no relay `Follow` or `Undo`,
|
|
991
|
+
and no `Accept` or `Reject` answering an inbox. This holds whatever caused
|
|
992
|
+
the change: the watcher, the admin, the scheduler, `geekity resend` or a
|
|
993
|
+
reply context arriving.
|
|
994
|
+
- No webmention. Endpoint discovery is skipped too.
|
|
995
|
+
- No rssCloud or WebSub ping.
|
|
996
|
+
- No IndexNow submission.
|
|
997
|
+
- No email.
|
|
998
|
+
|
|
999
|
+
Reads still go out: reply contexts and cited pages, actor profiles, oEmbed,
|
|
1000
|
+
avatars, the check of an incoming webmention, and a plugin's `host.fetch`. A
|
|
1001
|
+
read tells nobody about the site, and without them it could not show its
|
|
1002
|
+
replies or profiles. Inbound federation is accepted: follows, replies, likes
|
|
1003
|
+
and webmentions are stored as on a live site, and the `Accept` a follow would
|
|
1004
|
+
get is held. Plugins need no switch of their own. A plugin's federation hands
|
|
1005
|
+
what it receives to the site's inbox, so its answers are held the same way.
|
|
1006
|
+
|
|
1007
|
+
Each held side effect is a line in `data/dev-mode.jsonl`: when, what kind,
|
|
1008
|
+
what would have been sent, and to whom. Tools > Dev mode in the admin lists
|
|
1009
|
+
them, newest first. Every admin screen shows a banner while the mode is on,
|
|
1010
|
+
and `geekity serve` says so when it boots.
|
|
1011
|
+
|
|
1012
|
+
```sh
|
|
1013
|
+
$ geekity dev-mode on
|
|
1014
|
+
Dev mode is on: nothing the site does is sent until `geekity dev-mode off`.
|
|
1015
|
+
$ geekity dev-mode status
|
|
1016
|
+
Dev mode is on.
|
|
1017
|
+
$ geekity dev-mode off
|
|
1018
|
+
Dev mode is off: the site is live. Nothing held while it was on will be sent.
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
`devMode: true` in the config, or `GEEKITY_DEV_MODE=true`, starts a site in
|
|
1022
|
+
dev mode: boot writes `data/dev-mode.json`, and the file keeps the mode on
|
|
1023
|
+
from then on. Losing the variable does not take the site live. Only
|
|
1024
|
+
`geekity dev-mode off` does, and it refuses while the config or the
|
|
1025
|
+
environment still asks for dev mode, because the next boot would turn it on
|
|
1026
|
+
again. The running site checks the file before every send, so going live
|
|
1027
|
+
needs no restart, unless the server was started with the variable: that
|
|
1028
|
+
process stays in dev mode until it restarts without it. Going live is a line
|
|
1029
|
+
in the record too.
|
|
1030
|
+
|
|
1031
|
+
Going live replays nothing. A held side effect is dropped, not queued, and no
|
|
1032
|
+
delivery or webmention is recorded as failed, so neither the Federation screen
|
|
1033
|
+
nor `geekity resend` offers it again. A post announced while the mode was on
|
|
1034
|
+
counts as announced, so its next edit goes out as an `Update`. A relay listed
|
|
1035
|
+
while the mode was on stays pending: press Retry on the Federation screen once
|
|
1036
|
+
the site is live.
|
|
1037
|
+
|
|
1038
|
+
Password recovery mail is held too, so in dev mode a password is reset from
|
|
1039
|
+
the shell. A peer that followed while the mode was on never got its `Accept`,
|
|
1040
|
+
and has to follow again.
|
|
1041
|
+
|
|
1042
|
+
### Dev mode on Docker
|
|
1043
|
+
|
|
1044
|
+
Set the variable in the service's environment before the first boot, so the
|
|
1045
|
+
container never starts live:
|
|
1046
|
+
|
|
1047
|
+
```yaml
|
|
1048
|
+
services:
|
|
1049
|
+
geekity:
|
|
1050
|
+
environment:
|
|
1051
|
+
GEEKITY_DEV_MODE: 'true'
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
To go live, remove the variable, recreate the container (the file in the
|
|
1055
|
+
`data` volume keeps the mode on through the restart), then run:
|
|
1056
|
+
|
|
1057
|
+
```sh
|
|
1058
|
+
docker compose exec geekity geekity dev-mode off
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
`geekity dev-mode status` in the same container says which state the site is
|
|
1062
|
+
in. The record is in the `data` volume at `data/dev-mode.jsonl`.
|
|
1063
|
+
|
|
921
1064
|
## Comments
|
|
922
1065
|
|
|
923
1066
|
A reader can answer a post on the page, and what they leave joins the same
|
|
924
1067
|
thread as the fediverse replies rather than sitting in a section of its own.
|
|
925
1068
|
|
|
926
1069
|
A comment is a file. `content/_data/comments/{slug}.json` holds one post's
|
|
927
|
-
comments
|
|
1070
|
+
comments (a slug that is not plain ASCII is percent-encoded in the name, so
|
|
1071
|
+
`i-♥-rss` is `i-%E2%99%A5-rss.json`) — id, source, kind, status, author, the Markdown and the HTML it
|
|
928
1072
|
rendered to, when it was submitted, a salted hash of the address, and what it
|
|
929
1073
|
answers — so they are in git beside the posts, an Eleventy build of the same
|
|
930
1074
|
directory shows them, and the `comments` table is an index emptied and read
|
|
@@ -975,6 +1119,68 @@ A checker that throws is treated as having no opinion and logged, so a service
|
|
|
975
1119
|
that is down never stops a site taking comments. `/admin/comments` is the
|
|
976
1120
|
moderation queue and the dashboard carries the number waiting.
|
|
977
1121
|
|
|
1122
|
+
### A comment's own page
|
|
1123
|
+
|
|
1124
|
+
A comment left through the form has a page of its own at `/comment/{id}/`,
|
|
1125
|
+
the comment's id percent-encoded. That page is the comment's URL in the thread
|
|
1126
|
+
and in both comments feeds, so a site that replies to the comment fetches the
|
|
1127
|
+
comment rather than the whole post. The `#comment-{id}` anchor on the post
|
|
1128
|
+
stays, so an old link still scrolls to it. A webmention or a fediverse reply
|
|
1129
|
+
has no such page: its URL is where it came from.
|
|
1130
|
+
|
|
1131
|
+
The page holds the comment as its one `h-entry`, with `u-in-reply-to` the post
|
|
1132
|
+
or the comment it answers. Above it is the thread from the post down, each step
|
|
1133
|
+
a nested `u-in-reply-to h-cite`, and below it the replies to it at every depth.
|
|
1134
|
+
An ancestor a reader may not see is a placeholder saying the comment is no
|
|
1135
|
+
longer shown, and the thread on the post prints the same placeholder above an
|
|
1136
|
+
approved reply to a pending, spam or deleted comment. The page answers only an approved comment, and only as the post
|
|
1137
|
+
would: 410 once the post is deleted, 404 while the post is not public or not
|
|
1138
|
+
showing its conversation. It is `noindex` and absent from the sitemap, the post
|
|
1139
|
+
feeds and search. Backlog doc-6 has the rules.
|
|
1140
|
+
|
|
1141
|
+
### Replying while signed in
|
|
1142
|
+
|
|
1143
|
+
A comment is something somebody else says on the site. A post is something a
|
|
1144
|
+
signed-in user says. So a reply written by somebody signed in to the site, in
|
|
1145
|
+
the thread or in the Reply box on the moderation screen, is a reply post, not a
|
|
1146
|
+
comment. It is one file under `posts/`, saved
|
|
1147
|
+
the way the editor and Micropub save one, and the thread shows it inline under
|
|
1148
|
+
what it answers. Its `in-reply-to` is what it answers:
|
|
1149
|
+
|
|
1150
|
+
| Answering | `in-reply-to` | It also |
|
|
1151
|
+
| ------------------ | --------------------------------- | -------------------------------------------------------- |
|
|
1152
|
+
| The post | The post's permalink | |
|
|
1153
|
+
| A native comment | The comment's `/comment/{id}/` | Emails the commenter once, if they asked about replies. |
|
|
1154
|
+
| A webmention reply | The page the webmention came from | Sends that page a webmention. |
|
|
1155
|
+
| A fediverse reply | The note's id | Federates in reply to the note, addressed to its author. |
|
|
1156
|
+
| A reply post | The reply post's permalink | |
|
|
1157
|
+
|
|
1158
|
+
The signed-in form and the moderation screen's Reply box have an "Include in
|
|
1159
|
+
posts and feeds" checkbox, unchecked by default. Unchecked, the reply post is [unlisted](#unlisted-posts): its own page
|
|
1160
|
+
and the thread, but no listing or post feed. Checked, it is public. It reaches
|
|
1161
|
+
whoever it answers either way. A visitor's form has no such box.
|
|
1162
|
+
|
|
1163
|
+
Somebody signed in gets a Reply link on every reply in a thread, webmentions
|
|
1164
|
+
and fediverse replies included. A visitor gets one on native comments only. A
|
|
1165
|
+
visitor's reply is always a native comment, so a visitor answering a fediverse
|
|
1166
|
+
reply sends nothing over ActivityPub.
|
|
1167
|
+
|
|
1168
|
+
A reply post written in the editor or over Micropub joins a thread the same way
|
|
1169
|
+
when its `in-reply-to` names the post or anything in its thread: a comment's
|
|
1170
|
+
page or `#comment-` anchor, a webmention's page, a note's id. Its reply context
|
|
1171
|
+
quotes that comment from the site's own records instead of the page it is on,
|
|
1172
|
+
and says "In reply to a comment by" its writer. A reply post answering one of
|
|
1173
|
+
the site's own posts or pages takes that post's title, words and author from
|
|
1174
|
+
the index too, so the site never fetches its own page. The commenter's email
|
|
1175
|
+
goes once for a reply post from any of these doors, sent when the reply post is
|
|
1176
|
+
first published and never again when it is edited, trashed and restored, or the
|
|
1177
|
+
site restarts. In the thread, a reply post's author link carries no
|
|
1178
|
+
`rel="nofollow ugc"`, which is kept for visitors, webmentions and the
|
|
1179
|
+
fediverse. A
|
|
1180
|
+
webmention or a fediverse reply to a reply post threads under it in the
|
|
1181
|
+
original thread too. Comments written before this stay comments. Backlog doc-6
|
|
1182
|
+
and decision-47 have the rules.
|
|
1183
|
+
|
|
978
1184
|
### Being told about one
|
|
979
1185
|
|
|
980
1186
|
With mail configured, a comment or a webmention entering the queue emails every
|
|
@@ -1107,6 +1313,19 @@ it was showing. One outcome is recorded per link (`sent`, `none` for a page that
|
|
|
1107
1313
|
takes none, or `failed`), shown per post on `/admin/federation`, and the Resend
|
|
1108
1314
|
button there sends them again from the file as it now reads.
|
|
1109
1315
|
|
|
1316
|
+
**Links to the site's own pages** send no webmention. A post that cites another
|
|
1317
|
+
post on the same site does not go through moderation, does not add to a comment
|
|
1318
|
+
count and does not make an HTTP request to the site itself. The index records
|
|
1319
|
+
the link instead. The linked post or page then lists the linking one among its
|
|
1320
|
+
backlinks, which the default theme prints as "Linked from" under it, apart from
|
|
1321
|
+
the comments. A link counts for the document the site serves at its URL: a link
|
|
1322
|
+
to `/` counts for the page the Reading setting makes the homepage, and a link
|
|
1323
|
+
to a URL the site redirects, such as an imported `/?p=123`, counts for the
|
|
1324
|
+
document the redirect leads to. Redirects are followed when the backlinks are
|
|
1325
|
+
read, so editing a redirect file moves a backlink without touching the post
|
|
1326
|
+
that holds the link. A draft, future-dated, unlisted, private or trashed post is never
|
|
1327
|
+
a backlink. See `themes/default/README.md` for the `backlinks` context.
|
|
1328
|
+
|
|
1110
1329
|
**Receiving** advertises `/_geekity/webmention` two ways — a `Link` header on
|
|
1111
1330
|
every representation of a document, and a `<link rel="webmention">` in the head
|
|
1112
1331
|
— and takes a form `POST` of `source` and `target`. It answers `202` for
|
|
@@ -1119,6 +1338,12 @@ queue as everything else — through the same `commentChecker` seam, so a checke
|
|
|
1119
1338
|
can tell one from a form submission by its `source`. Akismet is told it is a
|
|
1120
1339
|
`webmention` rather than a `comment`.
|
|
1121
1340
|
|
|
1341
|
+
A reply whose `u-in-reply-to` names a reply already on the post (a comment by
|
|
1342
|
+
its own page or its `#comment-{id}` anchor, an earlier webmention by the page it
|
|
1343
|
+
was sent from, a fediverse reply by its url or id) threads under that reply, and
|
|
1344
|
+
moves with it when the source is edited and sent again. A webmention sent to a
|
|
1345
|
+
comment's page, `/comment/{id}/`, lands on that comment's post.
|
|
1346
|
+
|
|
1122
1347
|
The source URL is its identity. A page that sends its webmention again updates
|
|
1123
1348
|
what it left rather than adding a second, and one whose link has gone — or which
|
|
1124
1349
|
answers 404 or 410 — takes it away. Closing rules do not apply: a post that
|
|
@@ -1687,8 +1912,8 @@ The rule works for pages too.
|
|
|
1687
1912
|
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1688
1913
|
| Its permalink, in every representation | 200, with `X-Robots-Tag: noindex`. The default theme also prints `<meta name="robots" content="noindex">`. |
|
|
1689
1914
|
| The home page, tag, category and author archives, the `archive: true` page | Absent, and not counted in a pager or a tag count. |
|
|
1690
|
-
| Previous and next links under other posts,
|
|
1691
|
-
| Every RSS, Atom and JSON feed, and the site-wide comments feed | Absent. Saving one pings no notify server.
|
|
1915
|
+
| Previous and next links under other posts, a front page's `newestPosts` | Absent. |
|
|
1916
|
+
| Every RSS, Atom and JSON feed, and the site-wide comments feed | Absent. Saving one pings no notify server. An unlisted reply post shown in a thread is still in that thread's comments feeds. |
|
|
1692
1917
|
| The sitemap, search (HTML and JSON) and `llms.txt` | Absent. |
|
|
1693
1918
|
| IndexNow | Not submitted. A public post that becomes unlisted is submitted once, so a search engine reads the `noindex`. |
|
|
1694
1919
|
| The ActivityPub outbox and featured collection | Absent. |
|
|
@@ -2032,9 +2257,38 @@ choice is WordPress's own, and so are its two answers:
|
|
|
2032
2257
|
the default, and it is what an empty `homepage` means.
|
|
2033
2258
|
- **A static page.** The page is served at `/`, its own permalink answers `301`
|
|
2034
2259
|
to `/` so the front page has one URL, and the menu links it at `/`. The
|
|
2035
|
-
default theme draws it with `layouts/front-page.njk
|
|
2036
|
-
|
|
2037
|
-
|
|
2260
|
+
default theme draws it with `layouts/front-page.njk`, which prints the
|
|
2261
|
+
page's own title and words and nothing else (decision-44). It lists no
|
|
2262
|
+
posts, prints no links and prints no bio.
|
|
2263
|
+
|
|
2264
|
+
A site that wants more on its front page, such as its newest posts or links
|
|
2265
|
+
to its archives, overrides `layouts/front-page.njk` in its own theme. The
|
|
2266
|
+
front page's context carries `newestPosts(count)`, a function that returns the
|
|
2267
|
+
newest `count` published posts as the same entries a listing prints. Drafts,
|
|
2268
|
+
future-dated, unlisted and trashed posts are left out. The theme chooses the
|
|
2269
|
+
count, and the query runs only when the theme calls it. For example, this
|
|
2270
|
+
override lists the five newest posts under the page's words:
|
|
2271
|
+
|
|
2272
|
+
```njk
|
|
2273
|
+
{% extends "layouts/base.njk" %}
|
|
2274
|
+
|
|
2275
|
+
{% block content %}
|
|
2276
|
+
<div class="page-body e-content">{{ content | safe }}</div>
|
|
2277
|
+
|
|
2278
|
+
<h2 class="section-title">Recent Posts</h2>
|
|
2279
|
+
{% set posts = newestPosts(5) %}
|
|
2280
|
+
{% set feedHeading = 3 %}
|
|
2281
|
+
{% include "partials/post-list.njk" %}
|
|
2282
|
+
{% endblock %}
|
|
2283
|
+
```
|
|
2284
|
+
|
|
2285
|
+
On a solo author site the default theme's base layout puts the author's
|
|
2286
|
+
`rel="me"` claims in the head of `/` as `<link rel="me">` elements, one for
|
|
2287
|
+
their author archive and one for each profile link, whatever the front page
|
|
2288
|
+
prints (decision-45). A static front page prints no h-card. A site that wants
|
|
2289
|
+
its homepage to carry a representative h-card writes one in the homepage's
|
|
2290
|
+
words, or includes `partials/bio.njk` with `bioHome` set in its override, as
|
|
2291
|
+
the [theme README](./themes/default/README.md#the-front-page) shows.
|
|
2038
2292
|
|
|
2039
2293
|
With a homepage set, a second pick gives the listing a page of its own: the
|
|
2040
2294
|
**posts page**. Its permalink carries the listing, with the page's own title
|
|
@@ -2439,6 +2693,11 @@ export default defineConfig({
|
|
|
2439
2693
|
A name that is not a header name, or a value that is empty or has a line break
|
|
2440
2694
|
in it, stops the site at boot. There is no environment variable for these.
|
|
2441
2695
|
|
|
2696
|
+
A policy that limits scripts has to allow whatever the site's theme adds to
|
|
2697
|
+
every page. For the Umami and Google Analytics snippets, the theme README's
|
|
2698
|
+
[Adding to every page](./themes/default/README.md#adding-to-every-page) lists
|
|
2699
|
+
the directives each one needs.
|
|
2700
|
+
|
|
2442
2701
|
Every response under `/admin`, static files and redirects included, gets three
|
|
2443
2702
|
stricter values in their place, whatever `securityHeaders` says:
|
|
2444
2703
|
|
|
@@ -2558,6 +2817,7 @@ Booting mounts the public site on the app. The routes are:
|
|
|
2558
2817
|
| `/author/{username}/feed/` and siblings | The same, for one person. |
|
|
2559
2818
|
| `/comments/feed/` | Every reply the inbox has been sent, as RSS 2.0. |
|
|
2560
2819
|
| `{permalink}feed/` | One post's replies, the same way. |
|
|
2820
|
+
| `/comment/{id}/` | One comment left through the form, on a page of its own: see [Comments](#comments). Noindex and on no list. |
|
|
2561
2821
|
| `/sitemap.xml` | Every public URL, for a search engine. |
|
|
2562
2822
|
| `/sitemap-{n}.xml` | One file of a sitemap too big to be a single one. |
|
|
2563
2823
|
| `/robots.txt` | What a crawler may have, and where the sitemap is. |
|
|
@@ -2742,9 +3002,20 @@ The list is in the file, so deleting `data/geekity.db` keeps it working.
|
|
|
2742
3002
|
|
|
2743
3003
|
A URL in the list only redirects while nothing else lives there: a new document
|
|
2744
3004
|
given that URL takes it over. Moving a document back to a URL in its list takes
|
|
2745
|
-
that URL off the list. A draft that was never published
|
|
2746
|
-
anything behind
|
|
2747
|
-
|
|
3005
|
+
that URL off the list. A draft that was never published and has no comments
|
|
3006
|
+
moves without leaving anything behind; one with comments was read at its URL,
|
|
3007
|
+
so it keeps a `redirect_from` like a published one. Correcting a published
|
|
3008
|
+
post's date files it under the new day but keeps its URL.
|
|
3009
|
+
|
|
3010
|
+
A moved document keeps its conversation. Its comments and stored webmentions
|
|
3011
|
+
move to its new slug, a reply post answering any URL in its `redirect_from`
|
|
3012
|
+
stays in its thread, and its feeds keep their guid: a post keeps its object id
|
|
3013
|
+
as its `activitypub.id`, and a page keeps the URL it left as its `guid`, so the
|
|
3014
|
+
`/replies/` feed a reader subscribed to keeps answering. The record of the
|
|
3015
|
+
webmentions a post has sent moves with it too. A move made by hand is followed
|
|
3016
|
+
the same way when the file names the old URL in `redirect_from`; a permalink
|
|
3017
|
+
changed by hand with no `redirect_from` leaves the old URL answering 404 and its
|
|
3018
|
+
comments where they were, until the file names it.
|
|
2748
3019
|
|
|
2749
3020
|
### Declared redirects
|
|
2750
3021
|
|
|
@@ -2797,6 +3068,25 @@ first request after the file changes, and it is skipped. The following entries a
|
|
|
2797
3068
|
A file that is not a JSON list serves no redirects and is reported the same
|
|
2798
3069
|
way.
|
|
2799
3070
|
|
|
3071
|
+
#### More than one file
|
|
3072
|
+
|
|
3073
|
+
Every `*.json` file in `content/_data/redirects/` is read after
|
|
3074
|
+
`redirects.json`, in name order, and served the same way. A tool that generates
|
|
3075
|
+
redirects, such as an import, writes a file of its own there and never touches
|
|
3076
|
+
the list a person keeps. A file there, or `redirects.json` itself, may also be
|
|
3077
|
+
an object that maps each `from` to its `to`, every one a `301`:
|
|
3078
|
+
|
|
3079
|
+
```json
|
|
3080
|
+
{
|
|
3081
|
+
"/?p=123": "/2026/01/hello/",
|
|
3082
|
+
"/old-section/": "/new-section/"
|
|
3083
|
+
}
|
|
3084
|
+
```
|
|
3085
|
+
|
|
3086
|
+
Where two files declare the same `from`, the one read first wins, so
|
|
3087
|
+
`redirects.json` wins over every file in the folder, and the other declaration
|
|
3088
|
+
is reported. A loop across files is found and skipped as within one.
|
|
3089
|
+
|
|
2800
3090
|
Every redirect the CMS sends carries `X-Redirect-By: Geekity CMS`. This
|
|
2801
3091
|
includes the declared redirects, moved URLs, trailing slashes, feed spellings
|
|
2802
3092
|
and the admin's redirects. Someone who traces a chain of redirects through a
|
|
@@ -2941,6 +3231,7 @@ so a site moving off it keeps every subscriber it had:
|
|
|
2941
3231
|
| `/category/{name}/feed/` | One category archive, the same three |
|
|
2942
3232
|
| `/comments/feed/` | Every reply the site has been sent, in RSS |
|
|
2943
3233
|
| `{permalink}feed/` | One post's replies, in RSS |
|
|
3234
|
+
| `/replies/{id or key}/` | One item's direct replies, in RSS |
|
|
2944
3235
|
|
|
2945
3236
|
`/feed/` is RSS because that is the format nearly every existing subscriber
|
|
2946
3237
|
holds. The two archive bases are settings (`tagBase`, `categoryBase`), so the
|
|
@@ -2980,15 +3271,17 @@ The three formats render one **feed item**, derived once per post, rather than
|
|
|
2980
3271
|
each reading the file for itself. So they agree about the three things a reader
|
|
2981
3272
|
actually keys on.
|
|
2982
3273
|
|
|
2983
|
-
**Identity.** A post is named by
|
|
2984
|
-
|
|
2985
|
-
matter stores for a post migrated from elsewhere
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
3274
|
+
**Identity.** A post is named by the first of three things it has: the `guid`
|
|
3275
|
+
its front matter sets, when that parses as a URL; else the `activitypub.id`
|
|
3276
|
+
the front matter stores for a post migrated from elsewhere; else its permalink
|
|
3277
|
+
(see [Federation](#federation)). That name is RSS's `guid`, Atom's `id` and
|
|
3278
|
+
JSON Feed's `id`. The permalink is always the link: RSS's `link`, Atom's
|
|
3279
|
+
`link rel="alternate"` and JSON Feed's `url`. The two are the same URL for
|
|
3280
|
+
every post born on this CMS, and differ only for one carrying a stored id or a
|
|
3281
|
+
`guid` — which is what lets a migrated post keep the name its WordPress
|
|
2990
3282
|
subscribers already hold. RSS's `isPermaLink` says which of the two a `guid`
|
|
2991
|
-
is: `true` when it
|
|
3283
|
+
is: `true` only when it equals the permalink, `false` for anything else, such
|
|
3284
|
+
as a stored `?p=813`.
|
|
2992
3285
|
|
|
2993
3286
|
**Terms.** Every format lists the post's categories and then its tags, in file
|
|
2994
3287
|
order, as one flat list: RSS's `category` elements, Atom's `category term`
|
|
@@ -3025,8 +3318,8 @@ do not know it to ignore:
|
|
|
3025
3318
|
The key is `_geekity` because an extension is named after its publisher, and
|
|
3026
3319
|
`in_reply_to` spells the microformats `in-reply-to` property the JSON way. A
|
|
3027
3320
|
post that is not a reply, or whose `in-reply-to` is not such a URL, carries
|
|
3028
|
-
neither. RSS 2.0 has no equivalent element, so an RSS item
|
|
3029
|
-
|
|
3321
|
+
neither. RSS 2.0 has no equivalent element, so an RSS item carries the target
|
|
3322
|
+
as the [source namespace][source-ns]'s `<source:inReplyTo>`.
|
|
3030
3323
|
|
|
3031
3324
|
**Languages.** Every feed declares the site's `language` once: RSS as the
|
|
3032
3325
|
channel's `language`, Atom as the feed's `xml:lang` and JSON Feed as its
|
|
@@ -3056,17 +3349,21 @@ avatar when the site has one.
|
|
|
3056
3349
|
An item carries `title`, `link`, `guid`, `pubDate` in RFC 822, `dc:creator` from
|
|
3057
3350
|
the post's author or the site's, one `category` per term, `description` holding
|
|
3058
3351
|
the summary, `content:encoded` holding the whole rendered post, and
|
|
3059
|
-
`source:markdown` holding the Markdown the post was written from.
|
|
3060
|
-
|
|
3352
|
+
`source:markdown` holding the Markdown the post was written from. A reply also
|
|
3353
|
+
carries `source:inReplyTo` holding the URL it answers. The `guid`, the terms
|
|
3354
|
+
and the summary are the ones described above.
|
|
3061
3355
|
|
|
3062
3356
|
`source:markdown` is Dave Winer's [source namespace][source-ns]: a reader that
|
|
3063
3357
|
understands Markdown should render from it rather than from `content:encoded`.
|
|
3064
3358
|
It is the same text the ActivityStreams `Article` carries as its `source`.
|
|
3065
3359
|
|
|
3066
|
-
Every item also says where its comments are
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3360
|
+
Every item also says where its comments are: `comments` is the page to read
|
|
3361
|
+
them on and `wfw:commentRss` (the [Well-Formed Web][wfw-ns] comment API) is the
|
|
3362
|
+
feed of the whole thread to poll. A post with replies also carries
|
|
3363
|
+
`source:comments`, whose `count` is how many replies answer the post directly
|
|
3364
|
+
and whose `feedUrl` is its `/replies/{key}/` feed holding only those (see
|
|
3365
|
+
[Walking a thread](#walking-a-thread)). A post nobody answered carries no
|
|
3366
|
+
`source:comments`.
|
|
3070
3367
|
|
|
3071
3368
|
When the site has a license (Settings > General, or `license` in `site.json`),
|
|
3072
3369
|
the channel carries a `creativeCommons:license` holding its URL, and every item
|
|
@@ -3087,34 +3384,68 @@ post as under it.
|
|
|
3087
3384
|
|
|
3088
3385
|
### Comments
|
|
3089
3386
|
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3387
|
+
The comments feeds publish a document's conversation, at the URLs WordPress
|
|
3388
|
+
publishes its own at: `{permalink}feed/` for one post or page, and
|
|
3389
|
+
`/comments/feed/` for the whole site. Both are read from the same conversation
|
|
3390
|
+
the page draws under the post, so they carry what a reader sees there:
|
|
3391
|
+
|
|
3392
|
+
- native comments left through the form, once a moderator approves them;
|
|
3393
|
+
- webmention replies and mentions;
|
|
3394
|
+
- fediverse replies, a `Create` of a `Note` whose `inReplyTo` names the post
|
|
3395
|
+
or a reply under it (see [Federation](#federation)), and the quotes of a
|
|
3396
|
+
post its author approved;
|
|
3397
|
+
- reply posts written on this site that answer the post or a reply under it.
|
|
3398
|
+
|
|
3399
|
+
Likes, boosts and reposts are counted on the page and are in no feed.
|
|
3095
3400
|
|
|
3096
3401
|
Both are RSS 2.0 and nothing else: a comments feed has no Atom or JSON spelling
|
|
3097
|
-
here, so `{permalink}feed/atom/` 404s. A post
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3402
|
+
here, so `{permalink}feed/atom/` 404s. A post has a comments feed whether or
|
|
3403
|
+
not it still takes comments. A page has one while it takes comments
|
|
3404
|
+
(`comments: true` in its front matter, and comments on for the site), which is
|
|
3405
|
+
when it shows its conversation; any other page's `{permalink}feed/` 404s, and
|
|
3406
|
+
its comments are left out of `/comments/feed/` too. A document with no replies
|
|
3407
|
+
answers an empty feed rather than a 404, because it exists and a reader that
|
|
3408
|
+
subscribed before anybody answered should keep polling. A permalink that is no
|
|
3409
|
+
published document 404s like any other. The page advertises its own feed with
|
|
3410
|
+
a `<link rel="alternate" type="application/rss+xml">` in its `<head>`.
|
|
3102
3411
|
|
|
3103
3412
|
A channel carries the usual `title` (`Comments on: {post}`), `link`,
|
|
3104
3413
|
`description`, `language`, `lastBuildDate`, `generator`, `atom:link rel="self"`
|
|
3105
3414
|
and the same notify-server elements every other feed carries. An item carries the author's name as its `title` and `dc:creator`,
|
|
3106
|
-
the reply's `url` as its `link`
|
|
3107
|
-
`published` time the
|
|
3108
|
-
`description` holding a plain-text excerpt
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3415
|
+
the reply's `url` as its `link` (a comment left through the form links to its
|
|
3416
|
+
own page at `/comment/{id}/`), a `guid`, the `published` time the reply gave (or
|
|
3417
|
+
when it arrived, if it gave none), `description` holding a plain-text excerpt
|
|
3418
|
+
and `content:encoded` holding the reply. The `guid` of a comment left through the
|
|
3419
|
+
form is its page, `isPermaLink="true"`; any other reply's is its own id: a
|
|
3420
|
+
fediverse note's id, a webmention's stored id, a reply post's object id. A
|
|
3421
|
+
comment imported from WordPress keeps the `guid` WordPress's comments feed
|
|
3422
|
+
published, so a reader sees nothing new after the move.
|
|
3423
|
+
`isPermaLink` is `true` exactly when the `guid` is the `link`.
|
|
3424
|
+
|
|
3425
|
+
Each item also carries, from the [source namespace][source-ns]:
|
|
3426
|
+
|
|
3427
|
+
- `source:inReplyTo`, the `guid` of what it answers in these feeds: the post's
|
|
3428
|
+
feed `guid` for an answer to the post, else the `guid` of the reply it
|
|
3429
|
+
answers. A reply under a comment a reader may not see (one waiting for a
|
|
3430
|
+
moderator, spam or deleted) names the nearest reply above it a reader can
|
|
3431
|
+
see, or the post, and is counted as that one's reply.
|
|
3432
|
+
- `source:comments`, only on an item with replies: `count` is how many answer
|
|
3433
|
+
it directly and `feedUrl` its `/replies/` feed.
|
|
3434
|
+
- `<source url="…">Name</source>`, the core RSS element, when the site knows a
|
|
3435
|
+
feed for the author: a webmention author's `h-card` URL or site, or a site
|
|
3436
|
+
user's author feed `/author/{username}/feed/`. A comment left through the
|
|
3437
|
+
form and a fediverse reply carry none.
|
|
3438
|
+
|
|
3439
|
+
On the site-wide feed the title names the post or page as well: `{author} on
|
|
3440
|
+
{post}`, or `{author} replying to {parent author} on {post}` for an answer to
|
|
3441
|
+
another reply a reader can see.
|
|
3442
|
+
|
|
3443
|
+
A fediverse reply's author is named by the profile the site stored when that
|
|
3444
|
+
actor followed it, and otherwise the `@user@host` the actor's own URL implies. Naming them
|
|
3114
3445
|
properly would mean dereferencing the actor, which is a network round trip per
|
|
3115
3446
|
comment shown.
|
|
3116
3447
|
|
|
3117
|
-
**
|
|
3448
|
+
**A reply's HTML is sanitised before it is published.** It is markup a
|
|
3118
3449
|
stranger wrote, so it is tokenised and rebuilt from an allowlist rather than
|
|
3119
3450
|
passed through: `p`, `br`, `a`, `em`, `strong`, `del`, `code`, `pre`,
|
|
3120
3451
|
`blockquote` and the list elements survive; `script` and `style` are dropped
|
|
@@ -3123,10 +3454,37 @@ every attribute goes except an `a`'s `href`, which must be `http`, `https` or
|
|
|
3123
3454
|
`mailto` and is marked `rel="nofollow noopener noreferrer"`. `sanitizeCommentHtml`
|
|
3124
3455
|
is exported for a site that shows comments in its own templates.
|
|
3125
3456
|
|
|
3126
|
-
A reply to a
|
|
3127
|
-
disappears from `/comments/feed/`, and that
|
|
3457
|
+
A reply to a document that has since been unpublished or moved to the trash
|
|
3458
|
+
disappears from `/comments/feed/`, and that document's own feed 404s with it.
|
|
3128
3459
|
`feedSize` caps both feeds.
|
|
3129
3460
|
|
|
3461
|
+
### Walking a thread
|
|
3462
|
+
|
|
3463
|
+
`{permalink}feed/` is the whole thread, flat, which is what WordPress
|
|
3464
|
+
publishes and `wfw:commentRss` points at. A program that wants the tree
|
|
3465
|
+
(Dave Winer's [rss.chat][rss-chat] reads conversations this way) walks it one
|
|
3466
|
+
level at a time instead: from a post item's `source:comments` to a feed of the
|
|
3467
|
+
replies that answer the post directly, and from each of those items' own
|
|
3468
|
+
`source:comments` to the replies that answer it, until an item has none.
|
|
3469
|
+
Every reply a reader can see is reached once.
|
|
3470
|
+
|
|
3471
|
+
Each of those feeds is at `/replies/{segment}/`, RSS 2.0, newest first, its
|
|
3472
|
+
items in the same shape as the comments feeds:
|
|
3473
|
+
|
|
3474
|
+
- `/replies/{id}/` for a comment left through the form, by the same id as its
|
|
3475
|
+
page at `/comment/{id}/`, percent-encoded.
|
|
3476
|
+
- `/replies/{key}/` for everything else: a post or a page, a webmention, a
|
|
3477
|
+
fediverse reply or a reply post. A document has no id of its own and slugs
|
|
3478
|
+
are not unique across posts and pages, so the key is the first 16 hex digits
|
|
3479
|
+
of the SHA-256 of the item's feed `guid`.
|
|
3480
|
+
|
|
3481
|
+
A segment of exactly 16 lowercase hex digits is a key, and anything else is a
|
|
3482
|
+
comment id. A comment whose id happens to look like a key is named by its key
|
|
3483
|
+
instead, so no segment can name two items. An item nobody answered answers an
|
|
3484
|
+
empty feed; a key or an id that names nothing a reader can see 404s.
|
|
3485
|
+
|
|
3486
|
+
[rss-chat]: https://github.com/scripting/rss.chat
|
|
3487
|
+
|
|
3130
3488
|
### Atom and JSON Feed
|
|
3131
3489
|
|
|
3132
3490
|
An Atom entry carries `id` (the post's object id), `title`, `updated`,
|
|
@@ -3416,8 +3774,10 @@ commentsRssFeed({
|
|
|
3416
3774
|
|
|
3417
3775
|
`conversation.thread(document)` is the same reading for one post — the shape the
|
|
3418
3776
|
theme's `conversation.njk` is handed — `spokenIn` flattens it into the entries a
|
|
3419
|
-
feed carries,
|
|
3420
|
-
puts beside each item of a
|
|
3777
|
+
feed carries, each with the reply it sits under, `conversation.counts(documents)`
|
|
3778
|
+
is the number of direct replies `source:comments` puts beside each item of a
|
|
3779
|
+
post feed, and `conversation.repliesTo(segment)` is what one `/replies/` feed
|
|
3780
|
+
holds.
|
|
3421
3781
|
|
|
3422
3782
|
## Sitemap and robots.txt
|
|
3423
3783
|
|
|
@@ -3677,6 +4037,17 @@ The admin is not themed. Its templates and its static files live in a tree of
|
|
|
3677
4037
|
their own with a loader of their own, deliberately off this search path, so no
|
|
3678
4038
|
theme can shadow the login form or the CSRF field inside it.
|
|
3679
4039
|
|
|
4040
|
+
Analytics and other markup that goes on every page need no layout at all. The
|
|
4041
|
+
packaged `layouts/base.njk` includes `partials/head-end.njk` last in `<head>`
|
|
4042
|
+
and `partials/body-end.njk` last in `<body>`, both empty in the package, so a
|
|
4043
|
+
theme of `theme.json` and `partials/head-end.njk` holding the Umami or Google
|
|
4044
|
+
Analytics snippet adds it to every public HTML page and keeps every other
|
|
4045
|
+
packaged template. Neither is printed in a feed, a Markdown, text or JSON
|
|
4046
|
+
representation, mail, the admin or the editor's preview. The theme README's
|
|
4047
|
+
[Adding to every page](./themes/default/README.md#adding-to-every-page) has the
|
|
4048
|
+
Umami and GA4 example, the Docker volume, and what a Content-Security-Policy
|
|
4049
|
+
has to allow for them.
|
|
4050
|
+
|
|
3680
4051
|
The packaged theme is the Paper design (doc-9) on the andrewshell.org shell
|
|
3681
4052
|
(decision-16). Its stylesheet is written in Tailwind v4 in
|
|
3682
4053
|
`themes/default/src/style.css` and compiled to `themes/default/static/style.css`
|
|
@@ -3834,10 +4205,24 @@ Layouts are yours. The directory data files name them — `posts.json` says
|
|
|
3834
4205
|
Eleventy layout receives is the one this package's theme mirrors, so a layout
|
|
3835
4206
|
can often be moved across with only its `{% extends %}` removed.
|
|
3836
4207
|
|
|
3837
|
-
The
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
4208
|
+
The default theme's own layouts and partials build too, copied into
|
|
4209
|
+
`content/_includes/` as `layouts/` and `partials/`: the config adds the theme's
|
|
4210
|
+
other filters (`absoluteUrl`, `asset`, `host`, `fediverseHandle`) and the
|
|
4211
|
+
`newestPosts(count)` a front page override lists posts with, so a site theme's
|
|
4212
|
+
`front-page.njk` calling `{% set posts = newestPosts(5) %}` builds. They get
|
|
4213
|
+
only the context the config gives, though: `collections.menus` rather than
|
|
4214
|
+
`menus`, the `conversation` filter rather than the key, and no comment form,
|
|
4215
|
+
search or listing pagination.
|
|
4216
|
+
|
|
4217
|
+
Some things do not carry over. `/feed/`, `/feed/atom/` and `/feed/json/` are
|
|
4218
|
+
generated in code here, not by a template, so an Eleventy build needs its own;
|
|
4219
|
+
`/sitemap.xml` and `/robots.txt` are the same story, for the same reason. A
|
|
4220
|
+
comment's own page at `/comment/{id}/` is not written, since a build has no
|
|
4221
|
+
server to take a comment, so a comment links to its `#comment-{id}` anchor on
|
|
4222
|
+
the post. The site's own reply posts are not threaded under what they answer,
|
|
4223
|
+
and a document's backlinks are not listed: both read the CMS's index of every
|
|
4224
|
+
document, which the config's filters do not have. The config's header lists
|
|
4225
|
+
the same.
|
|
3841
4226
|
|
|
3842
4227
|
### The compatibility test
|
|
3843
4228
|
|