@geekity/cms 0.3.0 → 0.5.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 +2217 -214
- package/admin/components/fields.njk +165 -0
- package/admin/layouts/base.njk +1 -1
- package/admin/layouts/settings-page.njk +26 -0
- package/admin/layouts/shell.njk +17 -3
- package/admin/pages/account/forgot.njk +47 -0
- package/admin/pages/account/login.njk +26 -0
- package/admin/pages/account/reset.njk +38 -0
- package/admin/pages/account/setup.njk +28 -0
- package/admin/pages/appearance/themes.njk +69 -0
- package/admin/pages/comments/all.njk +133 -0
- package/admin/{layouts/dashboard.njk → pages/dashboard/home.njk} +8 -15
- package/admin/{layouts/document-conflict.njk → pages/documents/conflict.njk} +1 -0
- package/admin/{layouts/document-editor.njk → pages/documents/editor.njk} +75 -59
- package/admin/{layouts/document-list.njk → pages/documents/list.njk} +19 -2
- package/admin/pages/documents/taxonomy.njk +95 -0
- package/admin/pages/federation/followers.njk +238 -0
- package/admin/pages/federation/settings.njk +77 -0
- package/admin/pages/media/library.njk +160 -0
- package/admin/pages/messages/all.njk +110 -0
- package/admin/pages/navigation/menus.njk +129 -0
- package/admin/{layouts → pages}/placeholder.njk +5 -1
- package/admin/pages/settings/discussion.njk +99 -0
- package/admin/pages/settings/email.njk +203 -0
- package/admin/pages/settings/general.njk +29 -0
- package/admin/pages/settings/permalinks.njk +50 -0
- package/admin/pages/settings/reading.njk +64 -0
- package/admin/pages/tools/content-index.njk +89 -0
- package/admin/pages/users/edit.njk +239 -0
- package/admin/pages/users/list.njk +51 -0
- package/admin/pages/users/new.njk +41 -0
- package/admin/static/admin.css +558 -3
- package/admin/static/copy.js +52 -0
- package/admin/static/editor.js +16 -16
- package/admin/static/slug.js +61 -0
- package/dist/access-log.d.ts +44 -0
- package/dist/access-log.d.ts.map +1 -0
- package/dist/access-log.js +71 -0
- package/dist/access-log.js.map +1 -0
- package/dist/admin/accounts.d.ts +413 -0
- package/dist/admin/accounts.d.ts.map +1 -0
- package/dist/admin/accounts.js +680 -0
- package/dist/admin/accounts.js.map +1 -0
- package/dist/admin/appearance.d.ts +80 -0
- package/dist/admin/appearance.d.ts.map +1 -0
- package/dist/admin/appearance.js +136 -0
- package/dist/admin/appearance.js.map +1 -0
- package/dist/admin/comments.d.ts +90 -0
- package/dist/admin/comments.d.ts.map +1 -0
- package/dist/admin/comments.js +244 -0
- package/dist/admin/comments.js.map +1 -0
- package/dist/admin/credentials.d.ts +26 -2
- package/dist/admin/credentials.d.ts.map +1 -1
- package/dist/admin/credentials.js +35 -2
- package/dist/admin/credentials.js.map +1 -1
- package/dist/admin/documents.d.ts +117 -10
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +328 -41
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/federation.d.ts +161 -50
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +235 -93
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/formatting.d.ts +20 -0
- package/dist/admin/formatting.d.ts.map +1 -0
- package/dist/admin/formatting.js +72 -0
- package/dist/admin/formatting.js.map +1 -0
- package/dist/admin/headers.d.ts +65 -0
- package/dist/admin/headers.d.ts.map +1 -0
- package/dist/admin/headers.js +104 -0
- package/dist/admin/headers.js.map +1 -0
- package/dist/admin/index.d.ts +38 -12
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +25 -7
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/media.d.ts +168 -0
- package/dist/admin/media.d.ts.map +1 -0
- package/dist/admin/media.js +295 -0
- package/dist/admin/media.js.map +1 -0
- package/dist/admin/menu.d.ts +79 -0
- package/dist/admin/menu.d.ts.map +1 -0
- package/dist/admin/menu.js +143 -0
- package/dist/admin/menu.js.map +1 -0
- package/dist/admin/messages.d.ts +80 -0
- package/dist/admin/messages.d.ts.map +1 -0
- package/dist/admin/messages.js +152 -0
- package/dist/admin/messages.js.map +1 -0
- package/dist/admin/navigation.d.ts +155 -0
- package/dist/admin/navigation.d.ts.map +1 -0
- package/dist/admin/navigation.js +292 -0
- package/dist/admin/navigation.js.map +1 -0
- package/dist/admin/preview.d.ts.map +1 -1
- package/dist/admin/preview.js +31 -7
- package/dist/admin/preview.js.map +1 -1
- package/dist/admin/recovery.d.ts +92 -0
- package/dist/admin/recovery.d.ts.map +1 -0
- package/dist/admin/recovery.js +229 -0
- package/dist/admin/recovery.js.map +1 -0
- package/dist/admin/routes.d.ts +0 -20
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +164 -105
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/session.d.ts +17 -3
- package/dist/admin/session.d.ts.map +1 -1
- package/dist/admin/session.js +19 -5
- package/dist/admin/session.js.map +1 -1
- package/dist/admin/settings-discussion.d.ts +37 -0
- package/dist/admin/settings-discussion.d.ts.map +1 -0
- package/dist/admin/settings-discussion.js +117 -0
- package/dist/admin/settings-discussion.js.map +1 -0
- package/dist/admin/settings-email.d.ts +54 -0
- package/dist/admin/settings-email.d.ts.map +1 -0
- package/dist/admin/settings-email.js +189 -0
- package/dist/admin/settings-email.js.map +1 -0
- package/dist/admin/settings-federation.d.ts +48 -0
- package/dist/admin/settings-federation.d.ts.map +1 -0
- package/dist/admin/settings-federation.js +123 -0
- package/dist/admin/settings-federation.js.map +1 -0
- package/dist/admin/settings-general.d.ts +12 -0
- package/dist/admin/settings-general.d.ts.map +1 -0
- package/dist/admin/settings-general.js +48 -0
- package/dist/admin/settings-general.js.map +1 -0
- package/dist/admin/settings-page.d.ts +119 -0
- package/dist/admin/settings-page.d.ts.map +1 -0
- package/dist/admin/settings-page.js +132 -0
- package/dist/admin/settings-page.js.map +1 -0
- package/dist/admin/settings-pages.d.ts +30 -0
- package/dist/admin/settings-pages.d.ts.map +1 -0
- package/dist/admin/settings-pages.js +43 -0
- package/dist/admin/settings-pages.js.map +1 -0
- package/dist/admin/settings-permalinks.d.ts +16 -0
- package/dist/admin/settings-permalinks.d.ts.map +1 -0
- package/dist/admin/settings-permalinks.js +32 -0
- package/dist/admin/settings-permalinks.js.map +1 -0
- package/dist/admin/settings-reading.d.ts +34 -0
- package/dist/admin/settings-reading.d.ts.map +1 -0
- package/dist/admin/settings-reading.js +49 -0
- package/dist/admin/settings-reading.js.map +1 -0
- package/dist/admin/settings.d.ts +339 -96
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +600 -308
- package/dist/admin/settings.js.map +1 -1
- package/dist/admin/store.d.ts +606 -152
- package/dist/admin/store.d.ts.map +1 -1
- package/dist/admin/store.js +863 -268
- package/dist/admin/store.js.map +1 -1
- package/dist/admin/taxonomy.d.ts +114 -0
- package/dist/admin/taxonomy.d.ts.map +1 -0
- package/dist/admin/taxonomy.js +289 -0
- package/dist/admin/taxonomy.js.map +1 -0
- package/dist/admin/templates.d.ts +46 -14
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +46 -14
- package/dist/admin/templates.js.map +1 -1
- package/dist/admin/throttle.d.ts +84 -0
- package/dist/admin/throttle.d.ts.map +1 -0
- package/dist/admin/throttle.js +143 -0
- package/dist/admin/throttle.js.map +1 -0
- package/dist/admin/tools.d.ts +111 -0
- package/dist/admin/tools.d.ts.map +1 -0
- package/dist/admin/tools.js +182 -0
- package/dist/admin/tools.js.map +1 -0
- package/dist/admin/uploads.d.ts +28 -2
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +43 -4
- package/dist/admin/uploads.js.map +1 -1
- package/dist/admin/users.d.ts +100 -13
- package/dist/admin/users.d.ts.map +1 -1
- package/dist/admin/users.js +412 -39
- package/dist/admin/users.js.map +1 -1
- package/dist/cache.d.ts +141 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +207 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli.d.ts +14 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +429 -21
- package/dist/cli.js.map +1 -1
- package/dist/comments/akismet.d.ts +130 -0
- package/dist/comments/akismet.d.ts.map +1 -0
- package/dist/comments/akismet.js +274 -0
- package/dist/comments/akismet.js.map +1 -0
- package/dist/comments/conversation.d.ts +29 -0
- package/dist/comments/conversation.d.ts.map +1 -0
- package/dist/comments/conversation.js +60 -0
- package/dist/comments/conversation.js.map +1 -0
- package/dist/comments/form.d.ts +123 -0
- package/dist/comments/form.d.ts.map +1 -0
- package/dist/comments/form.js +79 -0
- package/dist/comments/form.js.map +1 -0
- package/dist/comments/index.d.ts +16 -0
- package/dist/comments/index.d.ts.map +1 -0
- package/dist/comments/index.js +10 -0
- package/dist/comments/index.js.map +1 -0
- package/dist/comments/markdown.d.ts +8 -0
- package/dist/comments/markdown.d.ts.map +1 -0
- package/dist/comments/markdown.js +85 -0
- package/dist/comments/markdown.js.map +1 -0
- package/dist/comments/moderate.d.ts +71 -0
- package/dist/comments/moderate.d.ts.map +1 -0
- package/dist/comments/moderate.js +88 -0
- package/dist/comments/moderate.js.map +1 -0
- package/dist/comments/policy.d.ts +57 -0
- package/dist/comments/policy.d.ts.map +1 -0
- package/dist/comments/policy.js +80 -0
- package/dist/comments/policy.js.map +1 -0
- package/dist/comments/records.d.ts +266 -0
- package/dist/comments/records.d.ts.map +1 -0
- package/dist/comments/records.js +487 -0
- package/dist/comments/records.js.map +1 -0
- package/dist/comments/routes.d.ts +60 -0
- package/dist/comments/routes.d.ts.map +1 -0
- package/dist/comments/routes.js +212 -0
- package/dist/comments/routes.js.map +1 -0
- package/dist/comments/submission.d.ts +297 -0
- package/dist/comments/submission.d.ts.map +1 -0
- package/dist/comments/submission.js +280 -0
- package/dist/comments/submission.js.map +1 -0
- package/dist/comments/viewer.d.ts +21 -0
- package/dist/comments/viewer.d.ts.map +1 -0
- package/dist/comments/viewer.js +45 -0
- package/dist/comments/viewer.js.map +1 -0
- package/dist/config.d.ts +225 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +121 -6
- package/dist/config.js.map +1 -1
- package/dist/contact/delivery.d.ts +60 -0
- package/dist/contact/delivery.d.ts.map +1 -0
- package/dist/contact/delivery.js +87 -0
- package/dist/contact/delivery.js.map +1 -0
- package/dist/contact/form.d.ts +130 -0
- package/dist/contact/form.d.ts.map +1 -0
- package/dist/contact/form.js +163 -0
- package/dist/contact/form.js.map +1 -0
- package/dist/contact/index.d.ts +17 -0
- package/dist/contact/index.d.ts.map +1 -0
- package/dist/contact/index.js +13 -0
- package/dist/contact/index.js.map +1 -0
- package/dist/contact/records.d.ts +124 -0
- package/dist/contact/records.d.ts.map +1 -0
- package/dist/contact/records.js +218 -0
- package/dist/contact/records.js.map +1 -0
- package/dist/contact/routes.d.ts +17 -0
- package/dist/contact/routes.d.ts.map +1 -0
- package/dist/contact/routes.js +127 -0
- package/dist/contact/routes.js.map +1 -0
- package/dist/contact/submission.d.ts +103 -0
- package/dist/contact/submission.d.ts.map +1 -0
- package/dist/contact/submission.js +166 -0
- package/dist/contact/submission.js.map +1 -0
- package/dist/content/document.d.ts +7 -2
- package/dist/content/document.d.ts.map +1 -1
- package/dist/content/document.js +1 -0
- package/dist/content/document.js.map +1 -1
- package/dist/content/index.d.ts +7 -2
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +4 -1
- package/dist/content/index.js.map +1 -1
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +24 -2
- package/dist/content/markdown.js.map +1 -1
- package/dist/content/parser.d.ts.map +1 -1
- package/dist/content/parser.js +11 -4
- package/dist/content/parser.js.map +1 -1
- package/dist/content/save.d.ts +5 -0
- package/dist/content/save.d.ts.map +1 -1
- package/dist/content/save.js +44 -1
- package/dist/content/save.js.map +1 -1
- package/dist/content/schedule.d.ts +125 -0
- package/dist/content/schedule.d.ts.map +1 -0
- package/dist/content/schedule.js +207 -0
- package/dist/content/schedule.js.map +1 -0
- package/dist/content/search.d.ts +64 -0
- package/dist/content/search.d.ts.map +1 -0
- package/dist/content/search.js +136 -0
- package/dist/content/search.js.map +1 -0
- package/dist/content/store.d.ts +220 -7
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +392 -67
- package/dist/content/store.js.map +1 -1
- package/dist/content/sync.d.ts +7 -1
- package/dist/content/sync.d.ts.map +1 -1
- package/dist/content/sync.js +18 -10
- package/dist/content/sync.js.map +1 -1
- package/dist/content/time.d.ts +56 -0
- package/dist/content/time.d.ts.map +1 -0
- package/dist/content/time.js +141 -0
- package/dist/content/time.js.map +1 -0
- package/dist/content/writer.d.ts.map +1 -1
- package/dist/content/writer.js +3 -0
- package/dist/content/writer.js.map +1 -1
- package/dist/env.d.ts +70 -5
- package/dist/env.d.ts.map +1 -1
- package/dist/federation/actor.d.ts +95 -41
- package/dist/federation/actor.d.ts.map +1 -1
- package/dist/federation/actor.js +180 -53
- package/dist/federation/actor.js.map +1 -1
- package/dist/federation/article.d.ts +44 -17
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +98 -46
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/delivery.d.ts +63 -15
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +154 -80
- package/dist/federation/delivery.js.map +1 -1
- package/dist/federation/federation.d.ts +22 -17
- package/dist/federation/federation.d.ts.map +1 -1
- package/dist/federation/federation.js +98 -77
- package/dist/federation/federation.js.map +1 -1
- package/dist/federation/followers.d.ts +3 -2
- package/dist/federation/followers.d.ts.map +1 -1
- package/dist/federation/followers.js +6 -5
- package/dist/federation/followers.js.map +1 -1
- package/dist/federation/import-wordpress.d.ts +128 -0
- package/dist/federation/import-wordpress.d.ts.map +1 -0
- package/dist/federation/import-wordpress.js +426 -0
- package/dist/federation/import-wordpress.js.map +1 -0
- package/dist/federation/inbox.d.ts +36 -13
- package/dist/federation/inbox.d.ts.map +1 -1
- package/dist/federation/inbox.js +120 -49
- package/dist/federation/inbox.js.map +1 -1
- package/dist/federation/index.d.ts +22 -10
- package/dist/federation/index.d.ts.map +1 -1
- package/dist/federation/index.js +13 -8
- package/dist/federation/index.js.map +1 -1
- package/dist/federation/keys.d.ts +98 -20
- package/dist/federation/keys.d.ts.map +1 -1
- package/dist/federation/keys.js +269 -39
- package/dist/federation/keys.js.map +1 -1
- package/dist/federation/mount.d.ts +46 -10
- package/dist/federation/mount.d.ts.map +1 -1
- package/dist/federation/mount.js +300 -28
- package/dist/federation/mount.js.map +1 -1
- package/dist/federation/paths.d.ts +23 -35
- package/dist/federation/paths.d.ts.map +1 -1
- package/dist/federation/paths.js +26 -39
- package/dist/federation/paths.js.map +1 -1
- package/dist/federation/records.d.ts +223 -0
- package/dist/federation/records.d.ts.map +1 -0
- package/dist/federation/records.js +506 -0
- package/dist/federation/records.js.map +1 -0
- package/dist/federation/relays.d.ts +114 -0
- package/dist/federation/relays.d.ts.map +1 -0
- package/dist/federation/relays.js +290 -0
- package/dist/federation/relays.js.map +1 -0
- package/dist/federation/replies.d.ts +80 -0
- package/dist/federation/replies.d.ts.map +1 -0
- package/dist/federation/replies.js +166 -0
- package/dist/federation/replies.js.map +1 -0
- package/dist/federation/wordpress.d.ts +131 -0
- package/dist/federation/wordpress.d.ts.map +1 -0
- package/dist/federation/wordpress.js +341 -0
- package/dist/federation/wordpress.js.map +1 -0
- package/dist/files/atomic.d.ts +103 -0
- package/dist/files/atomic.d.ts.map +1 -0
- package/dist/files/atomic.js +150 -0
- package/dist/files/atomic.js.map +1 -0
- package/dist/files/index.d.ts +3 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +2 -0
- package/dist/files/index.js.map +1 -0
- package/dist/forms/protection.d.ts +85 -0
- package/dist/forms/protection.d.ts.map +1 -0
- package/dist/forms/protection.js +116 -0
- package/dist/forms/protection.js.map +1 -0
- package/dist/images/icons.d.ts +59 -0
- package/dist/images/icons.d.ts.map +1 -0
- package/dist/images/icons.js +131 -0
- package/dist/images/icons.js.map +1 -0
- package/dist/images/index.d.ts +7 -0
- package/dist/images/index.d.ts.map +1 -0
- package/dist/images/index.js +4 -0
- package/dist/images/index.js.map +1 -0
- package/dist/images/markup.d.ts +47 -0
- package/dist/images/markup.d.ts.map +1 -0
- package/dist/images/markup.js +175 -0
- package/dist/images/markup.js.map +1 -0
- package/dist/images/paths.d.ts +34 -0
- package/dist/images/paths.d.ts.map +1 -0
- package/dist/images/paths.js +41 -0
- package/dist/images/paths.js.map +1 -0
- package/dist/images/variants.d.ts +104 -0
- package/dist/images/variants.d.ts.map +1 -0
- package/dist/images/variants.js +320 -0
- package/dist/images/variants.js.map +1 -0
- package/dist/index.d.ts +123 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +344 -25
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +18 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +34 -3
- package/dist/init.js.map +1 -1
- package/dist/mail/brevo.d.ts +35 -0
- package/dist/mail/brevo.d.ts.map +1 -0
- package/dist/mail/brevo.js +85 -0
- package/dist/mail/brevo.js.map +1 -0
- package/dist/mail/credentials.d.ts +62 -0
- package/dist/mail/credentials.d.ts.map +1 -0
- package/dist/mail/credentials.js +86 -0
- package/dist/mail/credentials.js.map +1 -0
- package/dist/mail/index.d.ts +15 -0
- package/dist/mail/index.d.ts.map +1 -0
- package/dist/mail/index.js +8 -0
- package/dist/mail/index.js.map +1 -0
- package/dist/mail/memory.d.ts +35 -0
- package/dist/mail/memory.d.ts.map +1 -0
- package/dist/mail/memory.js +29 -0
- package/dist/mail/memory.js.map +1 -0
- package/dist/mail/provider.d.ts +80 -0
- package/dist/mail/provider.d.ts.map +1 -0
- package/dist/mail/provider.js +23 -0
- package/dist/mail/provider.js.map +1 -0
- package/dist/mail/service.d.ts +156 -0
- package/dist/mail/service.d.ts.map +1 -0
- package/dist/mail/service.js +265 -0
- package/dist/mail/service.js.map +1 -0
- package/dist/mail/smtp.d.ts +39 -0
- package/dist/mail/smtp.d.ts.map +1 -0
- package/dist/mail/smtp.js +58 -0
- package/dist/mail/smtp.js.map +1 -0
- package/dist/mail/templates.d.ts +91 -0
- package/dist/mail/templates.d.ts.map +1 -0
- package/dist/mail/templates.js +69 -0
- package/dist/mail/templates.js.map +1 -0
- package/dist/notifications/comments.d.ts +66 -0
- package/dist/notifications/comments.d.ts.map +1 -0
- package/dist/notifications/comments.js +151 -0
- package/dist/notifications/comments.js.map +1 -0
- package/dist/notifications/digest.d.ts +135 -0
- package/dist/notifications/digest.d.ts.map +1 -0
- package/dist/notifications/digest.js +287 -0
- package/dist/notifications/digest.js.map +1 -0
- package/dist/notifications/index.d.ts +13 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +8 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/links.d.ts +60 -0
- package/dist/notifications/links.d.ts.map +1 -0
- package/dist/notifications/links.js +63 -0
- package/dist/notifications/links.js.map +1 -0
- package/dist/notifications/optouts.d.ts +48 -0
- package/dist/notifications/optouts.d.ts.map +1 -0
- package/dist/notifications/optouts.js +93 -0
- package/dist/notifications/optouts.js.map +1 -0
- package/dist/notifications/preferences.d.ts +158 -0
- package/dist/notifications/preferences.d.ts.map +1 -0
- package/dist/notifications/preferences.js +195 -0
- package/dist/notifications/preferences.js.map +1 -0
- package/dist/notifications/routes.d.ts +28 -0
- package/dist/notifications/routes.d.ts.map +1 -0
- package/dist/notifications/routes.js +184 -0
- package/dist/notifications/routes.js.map +1 -0
- package/dist/notifications/tokens.d.ts +60 -0
- package/dist/notifications/tokens.d.ts.map +1 -0
- package/dist/notifications/tokens.js +125 -0
- package/dist/notifications/tokens.js.map +1 -0
- package/dist/notify.d.ts +84 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +149 -0
- package/dist/notify.js.map +1 -0
- package/dist/web/archive.d.ts +49 -0
- package/dist/web/archive.d.ts.map +1 -0
- package/dist/web/archive.js +59 -0
- package/dist/web/archive.js.map +1 -0
- package/dist/web/assets.d.ts +6 -5
- package/dist/web/assets.d.ts.map +1 -1
- package/dist/web/assets.js +5 -7
- package/dist/web/assets.js.map +1 -1
- package/dist/web/authors.d.ts +143 -0
- package/dist/web/authors.d.ts.map +1 -0
- package/dist/web/authors.js +178 -0
- package/dist/web/authors.js.map +1 -0
- package/dist/web/comments.d.ts +60 -0
- package/dist/web/comments.d.ts.map +1 -0
- package/dist/web/comments.js +207 -0
- package/dist/web/comments.js.map +1 -0
- package/dist/web/context.d.ts +162 -28
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +123 -26
- package/dist/web/context.js.map +1 -1
- package/dist/web/conversation.d.ts +209 -0
- package/dist/web/conversation.d.ts.map +1 -0
- package/dist/web/conversation.js +557 -0
- package/dist/web/conversation.js.map +1 -0
- package/dist/web/documents.d.ts +36 -6
- package/dist/web/documents.d.ts.map +1 -1
- package/dist/web/documents.js +52 -10
- package/dist/web/documents.js.map +1 -1
- package/dist/web/feed-atom.d.ts +22 -0
- package/dist/web/feed-atom.d.ts.map +1 -0
- package/dist/web/feed-atom.js +83 -0
- package/dist/web/feed-atom.js.map +1 -0
- package/dist/web/feed-item.d.ts +145 -0
- package/dist/web/feed-item.d.ts.map +1 -0
- package/dist/web/feed-item.js +120 -0
- package/dist/web/feed-item.js.map +1 -0
- package/dist/web/feed-json.d.ts +79 -0
- package/dist/web/feed-json.d.ts.map +1 -0
- package/dist/web/feed-json.js +53 -0
- package/dist/web/feed-json.js.map +1 -0
- package/dist/web/feed-rss.d.ts +26 -0
- package/dist/web/feed-rss.d.ts.map +1 -0
- package/dist/web/feed-rss.js +203 -0
- package/dist/web/feed-rss.js.map +1 -0
- package/dist/web/feed-source.d.ts +244 -0
- package/dist/web/feed-source.d.ts.map +1 -0
- package/dist/web/feed-source.js +201 -0
- package/dist/web/feed-source.js.map +1 -0
- package/dist/web/feed-xml.d.ts +63 -0
- package/dist/web/feed-xml.d.ts.map +1 -0
- package/dist/web/feed-xml.js +116 -0
- package/dist/web/feed-xml.js.map +1 -0
- package/dist/web/feeds.d.ts +31 -96
- package/dist/web/feeds.d.ts.map +1 -1
- package/dist/web/feeds.js +94 -198
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/health.d.ts +31 -0
- package/dist/web/health.d.ts.map +1 -0
- package/dist/web/health.js +44 -0
- package/dist/web/health.js.map +1 -0
- package/dist/web/index.d.ts +30 -10
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +18 -7
- package/dist/web/index.js.map +1 -1
- package/dist/web/navigation.d.ts +195 -0
- package/dist/web/navigation.d.ts.map +1 -0
- package/dist/web/navigation.js +300 -0
- package/dist/web/navigation.js.map +1 -0
- package/dist/web/negotiate.d.ts +31 -0
- package/dist/web/negotiate.d.ts.map +1 -1
- package/dist/web/negotiate.js +31 -11
- package/dist/web/negotiate.js.map +1 -1
- package/dist/web/recent.d.ts +43 -0
- package/dist/web/recent.d.ts.map +1 -0
- package/dist/web/recent.js +37 -0
- package/dist/web/recent.js.map +1 -0
- package/dist/web/render.d.ts +203 -9
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +341 -17
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts +25 -8
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +854 -93
- package/dist/web/routes.js.map +1 -1
- package/dist/web/sanitize.d.ts +19 -0
- package/dist/web/sanitize.d.ts.map +1 -0
- package/dist/web/sanitize.js +272 -0
- package/dist/web/sanitize.js.map +1 -0
- package/dist/web/search.d.ts +79 -0
- package/dist/web/search.d.ts.map +1 -0
- package/dist/web/search.js +94 -0
- package/dist/web/search.js.map +1 -0
- package/dist/web/sitemap.d.ts +120 -0
- package/dist/web/sitemap.d.ts.map +1 -0
- package/dist/web/sitemap.js +199 -0
- package/dist/web/sitemap.js.map +1 -0
- package/dist/web/taxonomy.d.ts +152 -0
- package/dist/web/taxonomy.d.ts.map +1 -0
- package/dist/web/taxonomy.js +237 -0
- package/dist/web/taxonomy.js.map +1 -0
- package/dist/web/templates.d.ts +54 -16
- package/dist/web/templates.d.ts.map +1 -1
- package/dist/web/templates.js +109 -28
- package/dist/web/templates.js.map +1 -1
- package/dist/web/themes.d.ts +226 -0
- package/dist/web/themes.d.ts.map +1 -0
- package/dist/web/themes.js +314 -0
- package/dist/web/themes.js.map +1 -0
- package/dist/webmention/discovery.d.ts +51 -0
- package/dist/webmention/discovery.d.ts.map +1 -0
- package/dist/webmention/discovery.js +176 -0
- package/dist/webmention/discovery.js.map +1 -0
- package/dist/webmention/html.d.ts +71 -0
- package/dist/webmention/html.d.ts.map +1 -0
- package/dist/webmention/html.js +285 -0
- package/dist/webmention/html.js.map +1 -0
- package/dist/webmention/index.d.ts +12 -0
- package/dist/webmention/index.d.ts.map +1 -0
- package/dist/webmention/index.js +8 -0
- package/dist/webmention/index.js.map +1 -0
- package/dist/webmention/links.d.ts +18 -0
- package/dist/webmention/links.d.ts.map +1 -0
- package/dist/webmention/links.js +64 -0
- package/dist/webmention/links.js.map +1 -0
- package/dist/webmention/microformats.d.ts +93 -0
- package/dist/webmention/microformats.d.ts.map +1 -0
- package/dist/webmention/microformats.js +376 -0
- package/dist/webmention/microformats.js.map +1 -0
- package/dist/webmention/receive.d.ts +141 -0
- package/dist/webmention/receive.d.ts.map +1 -0
- package/dist/webmention/receive.js +264 -0
- package/dist/webmention/receive.js.map +1 -0
- package/dist/webmention/routes.d.ts +37 -0
- package/dist/webmention/routes.d.ts.map +1 -0
- package/dist/webmention/routes.js +75 -0
- package/dist/webmention/routes.js.map +1 -0
- package/dist/webmention/service.d.ts +94 -0
- package/dist/webmention/service.d.ts.map +1 -0
- package/dist/webmention/service.js +207 -0
- package/dist/webmention/service.js.map +1 -0
- package/package.json +11 -6
- package/templates/site/content/_data/site.json +8 -0
- package/templates/site/content/pages/about.md +7 -0
- package/templates/site/content/posts/2026-01-01-hello-world.md +5 -3
- package/templates/site/geekity.config.ts +11 -1
- package/templates/site/tsconfig.json +1 -1
- package/themes/default/README.md +1216 -50
- package/themes/default/layouts/404.njk +2 -3
- package/themes/default/layouts/author.njk +36 -0
- package/themes/default/layouts/base.njk +161 -31
- package/themes/default/layouts/category.njk +27 -0
- package/themes/default/layouts/front-page.njk +56 -0
- package/themes/default/layouts/home.njk +17 -1
- package/themes/default/layouts/page.njk +57 -5
- package/themes/default/layouts/post.njk +78 -19
- package/themes/default/layouts/search.njk +57 -0
- package/themes/default/layouts/tag.njk +3 -6
- package/themes/default/mail/comment-digest.html.njk +37 -0
- package/themes/default/mail/comment-digest.subject.njk +3 -0
- package/themes/default/mail/comment-digest.txt.njk +40 -0
- package/themes/default/mail/comment-pending.html.njk +27 -0
- package/themes/default/mail/comment-pending.subject.njk +2 -0
- package/themes/default/mail/comment-pending.txt.njk +24 -0
- package/themes/default/mail/comment-reply.html.njk +21 -0
- package/themes/default/mail/comment-reply.subject.njk +2 -0
- package/themes/default/mail/comment-reply.txt.njk +22 -0
- package/themes/default/mail/contact-message.html.njk +24 -0
- package/themes/default/mail/contact-message.subject.njk +5 -0
- package/themes/default/mail/contact-message.txt.njk +20 -0
- package/themes/default/mail/password-changed.html.njk +18 -0
- package/themes/default/mail/password-changed.subject.njk +2 -0
- package/themes/default/mail/password-changed.txt.njk +17 -0
- package/themes/default/mail/password-reset.html.njk +22 -0
- package/themes/default/mail/password-reset.subject.njk +2 -0
- package/themes/default/mail/password-reset.txt.njk +18 -0
- package/themes/default/mail/test.html.njk +14 -0
- package/themes/default/mail/test.subject.njk +2 -0
- package/themes/default/mail/test.txt.njk +12 -0
- package/themes/default/partials/archive.njk +27 -0
- package/themes/default/partials/bio.njk +71 -0
- package/themes/default/partials/comment-form.njk +137 -0
- package/themes/default/partials/contact-form.njk +88 -0
- package/themes/default/partials/conversation.njk +139 -0
- package/themes/default/partials/feeds.njk +26 -0
- package/themes/default/partials/jsonld.njk +120 -0
- package/themes/default/partials/menu.njk +32 -0
- package/themes/default/partials/pagination.njk +12 -6
- package/themes/default/partials/post-list.njk +51 -18
- package/themes/default/partials/search-form.njk +22 -0
- package/themes/default/partials/tags.njk +26 -5
- package/themes/default/static/highlight.js +4 -0
- package/themes/default/static/style.css +1142 -194
- package/themes/default/theme.json +9 -0
- package/admin/layouts/federation.njk +0 -145
- package/admin/layouts/login.njk +0 -23
- package/admin/layouts/settings.njk +0 -116
- package/admin/layouts/setup.njk +0 -27
- package/admin/layouts/users.njk +0 -91
- /package/admin/{layouts/_flash.njk → components/flash.njk} +0 -0
package/dist/admin/documents.js
CHANGED
|
@@ -2,11 +2,19 @@ import { mkdir, readFile, rename, rm } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { parseDocument } from '../content/parser.js';
|
|
4
4
|
import { contentFilePath, freeSlug, saveDocument } from '../content/save.js';
|
|
5
|
+
import { scheduledFor } from '../content/schedule.js';
|
|
5
6
|
import { defaultPermalink, slugify } from '../content/slug.js';
|
|
6
7
|
import { DuplicatePermalinkError, isTrashedPath, TRASH_DIRECTORY } from '../content/store.js';
|
|
8
|
+
import { calendarDayIn, DEFAULT_TIMEZONE, toUtcInstant, wallClockIn, zoneLabel, } from '../content/time.js';
|
|
7
9
|
import { normalizeBody, serializeDocument } from '../content/writer.js';
|
|
8
10
|
import { isPublicDocument } from '../web/documents.js';
|
|
11
|
+
import { COMMENTS_FRONT_MATTER_KEY } from '../comments/policy.js';
|
|
12
|
+
import { CONTACT_FRONT_MATTER_KEY } from '../contact/form.js';
|
|
13
|
+
import { userForAuthor } from '../web/authors.js';
|
|
14
|
+
import { findUserById, listUsers } from './accounts.js';
|
|
9
15
|
import { flash } from './flash.js';
|
|
16
|
+
import { formatInTimezone } from './formatting.js';
|
|
17
|
+
import { readSiteSettings } from './settings.js';
|
|
10
18
|
import { PREVIEW_PATH } from './preview.js';
|
|
11
19
|
import { ADMIN_PREFIX } from './session.js';
|
|
12
20
|
import { ADMIN_TEMPLATES } from './templates.js';
|
|
@@ -19,7 +27,30 @@ import { UPLOADS_PATH } from './uploads.js';
|
|
|
19
27
|
* there, alongside every other key somebody added by hand.
|
|
20
28
|
*/
|
|
21
29
|
export const EXCLUDE_KEY = 'eleventyExcludeFromCollections';
|
|
22
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* What the editor's Comments field can say: leave it to the site's rules, hold
|
|
32
|
+
* this document open, or close it.
|
|
33
|
+
*
|
|
34
|
+
* The values are the strings the form submits and the template renders as
|
|
35
|
+
* options, spelled once so the two cannot drift.
|
|
36
|
+
*/
|
|
37
|
+
export const COMMENT_SETTINGS = { site: '', open: 'open', closed: 'closed' };
|
|
38
|
+
/** A submitted Comments field, or the site default for anything else. */
|
|
39
|
+
function commentSetting(value) {
|
|
40
|
+
return value === COMMENT_SETTINGS.open || value === COMMENT_SETTINGS.closed
|
|
41
|
+
? value
|
|
42
|
+
: COMMENT_SETTINGS.site;
|
|
43
|
+
}
|
|
44
|
+
/** What a document's front matter already says about comments. */
|
|
45
|
+
function commentSettingOf(document) {
|
|
46
|
+
const own = document.extra[COMMENTS_FRONT_MATTER_KEY];
|
|
47
|
+
if (own === true)
|
|
48
|
+
return COMMENT_SETTINGS.open;
|
|
49
|
+
if (own === false)
|
|
50
|
+
return COMMENT_SETTINGS.closed;
|
|
51
|
+
return COMMENT_SETTINGS.site;
|
|
52
|
+
}
|
|
53
|
+
/** The posts screens: dated, tagged, categorised, filed under `posts/`. */
|
|
23
54
|
export const POST_KIND = {
|
|
24
55
|
type: 'post',
|
|
25
56
|
section: 'posts',
|
|
@@ -28,7 +59,9 @@ export const POST_KIND = {
|
|
|
28
59
|
plural: 'Posts',
|
|
29
60
|
dated: true,
|
|
30
61
|
tagged: true,
|
|
62
|
+
categorised: true,
|
|
31
63
|
excludable: false,
|
|
64
|
+
contactable: false,
|
|
32
65
|
};
|
|
33
66
|
/** The pages screens: standing content, no date prefix and no taxonomy. */
|
|
34
67
|
export const PAGE_KIND = {
|
|
@@ -39,7 +72,9 @@ export const PAGE_KIND = {
|
|
|
39
72
|
plural: 'Pages',
|
|
40
73
|
dated: false,
|
|
41
74
|
tagged: false,
|
|
75
|
+
categorised: false,
|
|
42
76
|
excludable: true,
|
|
77
|
+
contactable: true,
|
|
43
78
|
};
|
|
44
79
|
/** The URL of the editor for one document. */
|
|
45
80
|
export function editorPath(kind, slug) {
|
|
@@ -50,7 +85,13 @@ export function newEditorPath(kind) {
|
|
|
50
85
|
return `${kind.basePath}/new`;
|
|
51
86
|
}
|
|
52
87
|
/** The filters, in the order they are shown. */
|
|
53
|
-
export const DOCUMENT_FILTERS = [
|
|
88
|
+
export const DOCUMENT_FILTERS = [
|
|
89
|
+
'all',
|
|
90
|
+
'published',
|
|
91
|
+
'scheduled',
|
|
92
|
+
'draft',
|
|
93
|
+
'trash',
|
|
94
|
+
];
|
|
54
95
|
/** How many rows one page of a listing holds. */
|
|
55
96
|
export const DOCUMENTS_PER_PAGE = 25;
|
|
56
97
|
/** The filter a query string asked for, defaulting to everything live. */
|
|
@@ -62,7 +103,8 @@ export function listOptionsFor(kind, filter) {
|
|
|
62
103
|
return {
|
|
63
104
|
type: kind.type,
|
|
64
105
|
trashed: filter === 'trash',
|
|
65
|
-
...(filter === 'published' ? { draft: false } : {}),
|
|
106
|
+
...(filter === 'published' ? { draft: false, scheduled: false } : {}),
|
|
107
|
+
...(filter === 'scheduled' ? { draft: false, scheduled: true } : {}),
|
|
66
108
|
...(filter === 'draft' ? { draft: true } : {}),
|
|
67
109
|
};
|
|
68
110
|
}
|
|
@@ -72,7 +114,7 @@ export function listOptionsFor(kind, filter) {
|
|
|
72
114
|
* Both `/admin/posts` and `/admin/pages` are this function with a different
|
|
73
115
|
* {@link DocumentKind}: the screens, the templates and the write path are
|
|
74
116
|
* shared, and the kind decides what a file is called, whether the form has a
|
|
75
|
-
* date and
|
|
117
|
+
* date and a taxonomy, and which section of the navigation marks itself.
|
|
76
118
|
*/
|
|
77
119
|
export function mountDocumentScreens(app, options) {
|
|
78
120
|
const { kind, render } = options;
|
|
@@ -89,6 +131,7 @@ export function mountDocumentScreens(app, options) {
|
|
|
89
131
|
const rows = found.slice(0, DOCUMENTS_PER_PAGE);
|
|
90
132
|
return render(c, ADMIN_TEMPLATES.documentList, {
|
|
91
133
|
section: kind.section,
|
|
134
|
+
child: 'all',
|
|
92
135
|
kind,
|
|
93
136
|
filter,
|
|
94
137
|
filters: DOCUMENT_FILTERS.map((name) => ({
|
|
@@ -97,7 +140,7 @@ export function mountDocumentScreens(app, options) {
|
|
|
97
140
|
url: listingUrl(kind, name, 1),
|
|
98
141
|
current: name === filter,
|
|
99
142
|
})),
|
|
100
|
-
documents: rows.map((document) => listRow(kind, document)),
|
|
143
|
+
documents: rows.map((document) => listRow(kind, document, c.var.store.now(), pageRole(kind, document, c))),
|
|
101
144
|
newUrl: newEditorPath(kind),
|
|
102
145
|
returnUrl: listingUrl(kind, filter, pageNumber),
|
|
103
146
|
page: pageNumber,
|
|
@@ -111,14 +154,14 @@ export function mountDocumentScreens(app, options) {
|
|
|
111
154
|
kind,
|
|
112
155
|
render,
|
|
113
156
|
document: undefined,
|
|
114
|
-
form: blankForm(kind),
|
|
157
|
+
form: blankForm(kind, siteTimezone(c), c.var.store.now()),
|
|
115
158
|
}));
|
|
116
159
|
app.post(newEditorPath(kind), async (c) => saveFromForm(c, { kind, render, document: undefined, body: await c.req.parseBody() }));
|
|
117
160
|
app.get(`${kind.basePath}/:slug`, (c) => {
|
|
118
161
|
const document = findBySlug(c.var.store, kind, c.req.param('slug'));
|
|
119
162
|
if (document === undefined)
|
|
120
163
|
return c.notFound();
|
|
121
|
-
return renderEditor(c, { kind, render, document, form: formFor(document) });
|
|
164
|
+
return renderEditor(c, { kind, render, document, form: formFor(document, siteTimezone(c)) });
|
|
122
165
|
});
|
|
123
166
|
app.post(`${kind.basePath}/:slug`, async (c) => {
|
|
124
167
|
const document = findBySlug(c.var.store, kind, c.req.param('slug'));
|
|
@@ -151,9 +194,13 @@ async function saveFromForm(c, options) {
|
|
|
151
194
|
permalink: text(body['permalink']).trim(),
|
|
152
195
|
date: text(body['date']).trim(),
|
|
153
196
|
tags: text(body['tags']).trim(),
|
|
197
|
+
categories: text(body['categories']).trim(),
|
|
154
198
|
description: text(body['description']).trim(),
|
|
199
|
+
author: text(body['author']).trim(),
|
|
155
200
|
draft: body['draft'] !== undefined,
|
|
156
201
|
exclude: body['exclude'] !== undefined,
|
|
202
|
+
contact: body['contact'] !== undefined,
|
|
203
|
+
comments: commentSetting(text(body['comments'])),
|
|
157
204
|
body: normalizeBody(text(body['body'])),
|
|
158
205
|
hash: text(body['hash']),
|
|
159
206
|
};
|
|
@@ -173,19 +220,57 @@ async function saveFromForm(c, options) {
|
|
|
173
220
|
}
|
|
174
221
|
if (form.title === '')
|
|
175
222
|
return refuse(`A ${kind.singular} needs a title.`);
|
|
176
|
-
const
|
|
177
|
-
|
|
223
|
+
const timezone = siteTimezone(c);
|
|
224
|
+
// decision-11: what the file gets is a UTC instant, and an offset-less field
|
|
225
|
+
// is the site's own wall clock rather than the server's.
|
|
226
|
+
const typed = kind.dated ? (form.date === '' ? store.now().toISOString() : form.date) : undefined;
|
|
227
|
+
if (typed !== undefined && !/^\d{4}-\d{2}-\d{2}/.test(typed)) {
|
|
178
228
|
return refuse('A date has to start with a year, a month and a day, like 2026-03-04.');
|
|
179
229
|
}
|
|
230
|
+
const date = typed === undefined ? undefined : toUtcInstant(typed, timezone);
|
|
231
|
+
if (typed !== undefined && date === undefined) {
|
|
232
|
+
return refuse('That date is not one anybody can read. Try 2026-03-04 09:00.');
|
|
233
|
+
}
|
|
180
234
|
const slug = slugify(form.slug) || slugify(form.title) || (document?.slug ?? '') || 'untitled';
|
|
181
235
|
const trashed = document !== undefined && isTrashedPath(document.path);
|
|
236
|
+
// The calendar day the document is filed under: the site zone's day at its
|
|
237
|
+
// date for a new one, and the day already in the filename for one whose date
|
|
238
|
+
// has not moved, so a zone changed later never moves a URL that exists.
|
|
239
|
+
const filed = filedDay({ date, document, timezone });
|
|
182
240
|
// A new document gets out of the way of anything that already holds its
|
|
183
241
|
// name; an existing one keeps the slug it was given.
|
|
184
242
|
const finalSlug = document === undefined
|
|
185
|
-
? await freeSlug({ contentDir, store, type: kind.type, slug, date })
|
|
243
|
+
? await freeSlug({ contentDir, store, type: kind.type, slug, date: filed })
|
|
186
244
|
: slug;
|
|
187
|
-
|
|
188
|
-
|
|
245
|
+
// decision-13: a published post's permalink is its name in the fediverse as
|
|
246
|
+
// well as on the web, and the two are one promise. Moving it hands every
|
|
247
|
+
// follower a second object and breaks every link somebody already shared, so
|
|
248
|
+
// the editor refuses. A draft has promised nothing yet and may still move;
|
|
249
|
+
// a site that really means to move a published post can still do it in the
|
|
250
|
+
// file, knowing what it costs.
|
|
251
|
+
const promised = promisedDocument(kind, document, store.now());
|
|
252
|
+
if (promised !== undefined) {
|
|
253
|
+
if (finalSlug !== promised.slug) {
|
|
254
|
+
return refuse(`The permalink of a published post is permanent, and the slug names the file it is filed under: this ${kind.singular} keeps the slug ${promised.slug}.`);
|
|
255
|
+
}
|
|
256
|
+
// An empty field is not a request to move: it is a form that carried no
|
|
257
|
+
// permalink, and the promised one is what it keeps. Only a field naming
|
|
258
|
+
// some other URL is somebody asking for the one thing this refuses.
|
|
259
|
+
const submitted = normalizePermalink(form.permalink);
|
|
260
|
+
if (submitted !== undefined && submitted !== promised.permalink) {
|
|
261
|
+
return refuse(`The permalink of a published post is permanent: this ${kind.singular} stays at ${promised.permalink}.`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const permalink = promised?.permalink ??
|
|
265
|
+
resolvePermalink({
|
|
266
|
+
kind,
|
|
267
|
+
form,
|
|
268
|
+
slug: finalSlug,
|
|
269
|
+
date: filed,
|
|
270
|
+
document,
|
|
271
|
+
timezone,
|
|
272
|
+
});
|
|
273
|
+
const target = documentPath({ kind, slug: finalSlug, date: filed, trashed });
|
|
189
274
|
if (document !== undefined) {
|
|
190
275
|
const conflict = await conflictWith(contentDir, document, form.hash);
|
|
191
276
|
if (conflict !== undefined) {
|
|
@@ -198,14 +283,15 @@ async function saveFromForm(c, options) {
|
|
|
198
283
|
const content = {
|
|
199
284
|
title: form.title,
|
|
200
285
|
...(date === undefined ? {} : { date }),
|
|
201
|
-
updated:
|
|
286
|
+
updated: store.now().toISOString(),
|
|
202
287
|
permalink,
|
|
203
288
|
tags: kind.tagged ? splitTags(form.tags) : [],
|
|
289
|
+
categories: kind.categorised ? splitTags(form.categories) : [],
|
|
204
290
|
draft,
|
|
205
291
|
...(form.description === '' ? {} : { description: form.description }),
|
|
206
|
-
...optional('author',
|
|
292
|
+
...optional('author', chosenAuthor(c, form.author, document)),
|
|
207
293
|
...optional('activitypub', document?.activitypub),
|
|
208
|
-
extra: resolveExtra(kind, document, form
|
|
294
|
+
extra: resolveExtra(kind, document, form),
|
|
209
295
|
body: form.body,
|
|
210
296
|
};
|
|
211
297
|
// The row for the old path goes first: a rename briefly leaves two rows
|
|
@@ -215,7 +301,7 @@ async function saveFromForm(c, options) {
|
|
|
215
301
|
store.remove(renamedFrom.path);
|
|
216
302
|
let saved;
|
|
217
303
|
try {
|
|
218
|
-
saved = await saveDocument({ contentDir, store, path: target, content });
|
|
304
|
+
saved = await saveDocument({ contentDir, store, path: target, content, timezone });
|
|
219
305
|
}
|
|
220
306
|
catch (error) {
|
|
221
307
|
// Nothing was written, so the row that was taken out of the way goes back.
|
|
@@ -241,13 +327,18 @@ async function saveFromForm(c, options) {
|
|
|
241
327
|
next: saved,
|
|
242
328
|
origin: 'admin',
|
|
243
329
|
});
|
|
244
|
-
flash(c, 'notice', savedMessage(kind, document, saved));
|
|
330
|
+
flash(c, 'notice', savedMessage(kind, document, saved, store.now()));
|
|
245
331
|
return c.redirect(editorPath(kind, saved.slug), 303);
|
|
246
332
|
}
|
|
247
333
|
/** What the flash says after a save, which depends on what the save did. */
|
|
248
|
-
function savedMessage(kind, previous, saved) {
|
|
334
|
+
function savedMessage(kind, previous, saved, now) {
|
|
249
335
|
if (saved.draft)
|
|
250
336
|
return `Draft saved: ${saved.title}`;
|
|
337
|
+
// A date in the future is not a refusal to publish, it is an instruction
|
|
338
|
+
// about when, and the flash has to say so or the author will think the
|
|
339
|
+
// Publish button did nothing.
|
|
340
|
+
if (scheduledFor(saved, now) !== undefined)
|
|
341
|
+
return `Scheduled: ${saved.title}`;
|
|
251
342
|
if (previous === undefined || previous.draft)
|
|
252
343
|
return `Published: ${saved.title}`;
|
|
253
344
|
return `Updated: ${saved.title}`;
|
|
@@ -257,6 +348,21 @@ function documentPath(input) {
|
|
|
257
348
|
const relative = contentFilePath({ type: input.kind.type, slug: input.slug, date: input.date });
|
|
258
349
|
return input.trashed ? `${TRASH_DIRECTORY}/${relative}` : relative;
|
|
259
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* The document whose permalink has already been promised, or `undefined` when
|
|
353
|
+
* nothing has been promised yet.
|
|
354
|
+
*
|
|
355
|
+
* A published post, and only a published post. decision-13 makes its permalink
|
|
356
|
+
* its ActivityStreams object id, so the URL is a name its followers, its
|
|
357
|
+
* replies and its feed subscribers are all holding; a draft, a trashed post
|
|
358
|
+
* and one whose date has not arrived have been shown to nobody, and a page
|
|
359
|
+
* federates nothing at all.
|
|
360
|
+
*/
|
|
361
|
+
function promisedDocument(kind, document, now) {
|
|
362
|
+
if (document === undefined || kind.type !== 'post')
|
|
363
|
+
return undefined;
|
|
364
|
+
return isPublicDocument(document, now) ? document : undefined;
|
|
365
|
+
}
|
|
260
366
|
/**
|
|
261
367
|
* The permalink to write.
|
|
262
368
|
*
|
|
@@ -276,20 +382,60 @@ function resolvePermalink(input) {
|
|
|
276
382
|
if (submitted === undefined)
|
|
277
383
|
return fallback;
|
|
278
384
|
const document = input.document;
|
|
279
|
-
if (document !== undefined &&
|
|
385
|
+
if (document !== undefined &&
|
|
386
|
+
submitted === previousDefaultPermalink(input.kind, document, input.timezone)) {
|
|
280
387
|
return fallback;
|
|
281
388
|
}
|
|
282
389
|
return submitted;
|
|
283
390
|
}
|
|
284
|
-
/**
|
|
285
|
-
|
|
391
|
+
/**
|
|
392
|
+
* The permalink a document would have had if it had never been customised:
|
|
393
|
+
* its own slug over the day it is filed under, which is the day in its
|
|
394
|
+
* filename rather than one re-derived from a setting that may have moved.
|
|
395
|
+
*/
|
|
396
|
+
function previousDefaultPermalink(kind, document, timezone) {
|
|
286
397
|
try {
|
|
287
|
-
return defaultPermalink({
|
|
398
|
+
return defaultPermalink({
|
|
399
|
+
type: kind.type,
|
|
400
|
+
slug: document.slug,
|
|
401
|
+
date: filedDay({ date: document.date, document, timezone }),
|
|
402
|
+
});
|
|
288
403
|
}
|
|
289
404
|
catch {
|
|
290
405
|
return undefined;
|
|
291
406
|
}
|
|
292
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* The calendar day a document is filed under, which is what its filename and
|
|
410
|
+
* its `/{yyyy}/{mm}/` permalink are cut from.
|
|
411
|
+
*
|
|
412
|
+
* For a new document it is the day the site's own zone was on at its date, so
|
|
413
|
+
* a post published at half past midnight on 1 October in Berlin is filed under
|
|
414
|
+
* October rather than the September UTC was still on.
|
|
415
|
+
*
|
|
416
|
+
* For a document whose date has not moved it is the day already in its
|
|
417
|
+
* filename. That is the whole of decision-11's promise that changing the
|
|
418
|
+
* timezone setting cannot move an existing URL: the instant is the same, the
|
|
419
|
+
* zone is a lens, and the day this post was filed under was decided once, when
|
|
420
|
+
* it was written, and is on disk where no setting can reach it.
|
|
421
|
+
*/
|
|
422
|
+
function filedDay(input) {
|
|
423
|
+
const { document } = input;
|
|
424
|
+
const date = input.date === undefined ? undefined : toUtcInstant(input.date, input.timezone);
|
|
425
|
+
if (date === undefined)
|
|
426
|
+
return undefined;
|
|
427
|
+
const was = document?.date === undefined ? undefined : toUtcInstant(document.date, input.timezone);
|
|
428
|
+
if (was === date) {
|
|
429
|
+
const existing = /(?:^|\/)(\d{4}-\d{2}-\d{2})-/.exec(document?.path ?? '')?.[1];
|
|
430
|
+
if (existing !== undefined)
|
|
431
|
+
return existing;
|
|
432
|
+
}
|
|
433
|
+
return calendarDayIn(date, input.timezone) ?? date;
|
|
434
|
+
}
|
|
435
|
+
/** The site's time zone, which is what every offset-less date in the admin means. */
|
|
436
|
+
function siteTimezone(c) {
|
|
437
|
+
return readSiteSettings(c.var.config.contentDir).timezone;
|
|
438
|
+
}
|
|
293
439
|
/** A submitted permalink as a URL path, or `undefined` when the field is empty. */
|
|
294
440
|
function normalizePermalink(value) {
|
|
295
441
|
const trimmed = value.trim();
|
|
@@ -314,17 +460,38 @@ function normalizePermalink(value) {
|
|
|
314
460
|
* the `false` is written, because a key somebody wrote by hand is not the
|
|
315
461
|
* editor's to delete.
|
|
316
462
|
*/
|
|
317
|
-
function resolveExtra(kind, document,
|
|
463
|
+
function resolveExtra(kind, document, form) {
|
|
318
464
|
const extra = { ...(document?.extra ?? {}) };
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
extra[
|
|
465
|
+
// The post's own answer about comments, or none at all: "site default" is
|
|
466
|
+
// the absence of the key rather than a value, because that is what every
|
|
467
|
+
// reader of it — the CMS, an Eleventy build, a person looking at the file —
|
|
468
|
+
// already understands, and writing `comments: null` would say nothing new.
|
|
469
|
+
if (form.comments === COMMENT_SETTINGS.open)
|
|
470
|
+
extra[COMMENTS_FRONT_MATTER_KEY] = true;
|
|
471
|
+
else if (form.comments === COMMENT_SETTINGS.closed)
|
|
472
|
+
extra[COMMENTS_FRONT_MATTER_KEY] = false;
|
|
473
|
+
else
|
|
474
|
+
delete extra[COMMENTS_FRONT_MATTER_KEY];
|
|
475
|
+
if (kind.excludable) {
|
|
476
|
+
if (form.exclude)
|
|
477
|
+
extra[EXCLUDE_KEY] = true;
|
|
478
|
+
else if (EXCLUDE_KEY in extra)
|
|
479
|
+
extra[EXCLUDE_KEY] = false;
|
|
480
|
+
}
|
|
481
|
+
if (kind.contactable) {
|
|
482
|
+
// Written or removed, never `false`: it is the CMS's own key, and absent
|
|
483
|
+
// and false mean the same thing to everything that reads it (TASK-56).
|
|
484
|
+
if (form.contact)
|
|
485
|
+
extra[CONTACT_FRONT_MATTER_KEY] = true;
|
|
486
|
+
else
|
|
487
|
+
delete extra[CONTACT_FRONT_MATTER_KEY];
|
|
488
|
+
}
|
|
325
489
|
return extra;
|
|
326
490
|
}
|
|
327
|
-
/**
|
|
491
|
+
/**
|
|
492
|
+
* A comma-separated taxonomy field as a list, without the blanks and the
|
|
493
|
+
* repeats. Tags and categories are both entered this way.
|
|
494
|
+
*/
|
|
328
495
|
export function splitTags(value) {
|
|
329
496
|
const tags = [];
|
|
330
497
|
for (const tag of value.split(',')) {
|
|
@@ -339,7 +506,62 @@ function currentUsername(c) {
|
|
|
339
506
|
const userId = c.var.session?.userId;
|
|
340
507
|
if (userId == null)
|
|
341
508
|
return undefined;
|
|
342
|
-
return c.var.
|
|
509
|
+
return findUserById(c.var.config.dataDir, userId)?.username;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* What a save writes into the front matter's `author`.
|
|
513
|
+
*
|
|
514
|
+
* The submitted value is resolved through the same rule the public site reads
|
|
515
|
+
* it by ({@link userForAuthor}), and what is written is that user's login: so
|
|
516
|
+
* a file carrying a display name from before decision-14 is rewritten as a
|
|
517
|
+
* username the first time somebody saves it, which is exactly the "until it is
|
|
518
|
+
* next saved" doc-2 promises.
|
|
519
|
+
*
|
|
520
|
+
* A value naming nobody this site has changes nothing. There is no form this
|
|
521
|
+
* CMS renders that could submit one — the select only ever offers the users
|
|
522
|
+
* and, for a file that names a stranger, that file's own value — so the case
|
|
523
|
+
* is either a hand-made request or a user deleted between the load and the
|
|
524
|
+
* save, and neither is a reason to reattribute somebody's post.
|
|
525
|
+
*/
|
|
526
|
+
function chosenAuthor(c, submitted, document) {
|
|
527
|
+
const named = userForAuthor(listUsers(c.var.config.dataDir), submitted);
|
|
528
|
+
if (named !== undefined)
|
|
529
|
+
return named.username;
|
|
530
|
+
return document?.author ?? currentUsername(c);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* The Author select: every user, whoever the document names, and — for a file
|
|
534
|
+
* naming somebody with no account here — that name too.
|
|
535
|
+
*
|
|
536
|
+
* The stranger's option is what keeps the editor honest. Without it, opening a
|
|
537
|
+
* post written by a colleague whose account has gone and pressing Update would
|
|
538
|
+
* quietly move the post to whoever happened to be first in the list; with it,
|
|
539
|
+
* the form submits back what the file says and changing the attribution stays
|
|
540
|
+
* a thing somebody chose to do.
|
|
541
|
+
*
|
|
542
|
+
* A new document starts on the signed-in user, which is what doc-5 says the
|
|
543
|
+
* editor does and what {@link blankForm} deliberately leaves to a request.
|
|
544
|
+
*/
|
|
545
|
+
export function authorChoices(c, current) {
|
|
546
|
+
const users = listUsers(c.var.config.dataDir);
|
|
547
|
+
const named = userForAuthor(users, current);
|
|
548
|
+
const chosen = named?.username ?? (current === '' ? currentUsername(c) : current);
|
|
549
|
+
const choices = users.map((user) => ({
|
|
550
|
+
value: user.username,
|
|
551
|
+
label: authorLabel(user),
|
|
552
|
+
chosen: user.username === chosen,
|
|
553
|
+
}));
|
|
554
|
+
// A name the file holds that is nobody here: offered last, and marked, so it
|
|
555
|
+
// is plain that the post is attributed to somebody this site cannot reach.
|
|
556
|
+
if (named === undefined && current !== '') {
|
|
557
|
+
choices.push({ value: current, label: `${current} (no account here)`, chosen: true });
|
|
558
|
+
}
|
|
559
|
+
return choices;
|
|
560
|
+
}
|
|
561
|
+
/** How one user reads in the select: their name, and the login behind it. */
|
|
562
|
+
function authorLabel(user) {
|
|
563
|
+
const displayName = user.profile?.displayName;
|
|
564
|
+
return displayName === undefined ? user.username : `${displayName} (${user.username})`;
|
|
343
565
|
}
|
|
344
566
|
/**
|
|
345
567
|
* Whether the file has moved on since the form loaded, per doc-1: files win,
|
|
@@ -385,16 +607,19 @@ function renderConflict(c, options) {
|
|
|
385
607
|
...(form.date === '' ? {} : { date: form.date }),
|
|
386
608
|
permalink: form.permalink === '' ? document.permalink : form.permalink,
|
|
387
609
|
tags: kind.tagged ? splitTags(form.tags) : [],
|
|
610
|
+
categories: kind.categorised ? splitTags(form.categories) : [],
|
|
388
611
|
draft: form.draft,
|
|
389
612
|
...(form.description === '' ? {} : { description: form.description }),
|
|
390
613
|
...optional('author', document.author),
|
|
391
614
|
...optional('activitypub', document.activitypub),
|
|
392
|
-
extra: resolveExtra(kind, document, form
|
|
615
|
+
extra: resolveExtra(kind, document, form),
|
|
393
616
|
body: form.body,
|
|
394
617
|
});
|
|
395
618
|
c.status(409);
|
|
396
619
|
return options.render(c, ADMIN_TEMPLATES.documentConflict, {
|
|
397
620
|
section: kind.section,
|
|
621
|
+
// Editing something that exists, so the listing is where the menu stands.
|
|
622
|
+
child: 'all',
|
|
398
623
|
kind,
|
|
399
624
|
form,
|
|
400
625
|
// The hash the file has now, so resubmitting this form is a deliberate
|
|
@@ -485,32 +710,56 @@ function optional(key, value) {
|
|
|
485
710
|
function text(value) {
|
|
486
711
|
return typeof value === 'string' ? value : '';
|
|
487
712
|
}
|
|
488
|
-
/**
|
|
489
|
-
|
|
713
|
+
/**
|
|
714
|
+
* The editor for a document that does not exist yet.
|
|
715
|
+
*
|
|
716
|
+
* The date field is filled in with the clock as the site's own zone reads it,
|
|
717
|
+
* not with an instant: what a form offers is what a form takes back, and per
|
|
718
|
+
* decision-11 an offset-less date in this field means the site's zone.
|
|
719
|
+
*/
|
|
720
|
+
export function blankForm(kind, timezone = DEFAULT_TIMEZONE, now = new Date()) {
|
|
490
721
|
return {
|
|
491
722
|
title: '',
|
|
492
723
|
slug: '',
|
|
493
724
|
permalink: '',
|
|
494
|
-
date: kind.dated ?
|
|
725
|
+
date: kind.dated ? wallClockIn(now, timezone) : '',
|
|
495
726
|
tags: '',
|
|
727
|
+
categories: '',
|
|
496
728
|
description: '',
|
|
729
|
+
// Empty rather than the signed-in user: `blankForm` is a pure function
|
|
730
|
+
// over a kind and a clock, and who is signed in is a fact about a request.
|
|
731
|
+
// {@link authorChoices} is where the default is applied.
|
|
732
|
+
author: '',
|
|
497
733
|
draft: false,
|
|
498
734
|
exclude: false,
|
|
735
|
+
contact: false,
|
|
736
|
+
comments: COMMENT_SETTINGS.site,
|
|
499
737
|
body: '',
|
|
500
738
|
hash: '',
|
|
501
739
|
};
|
|
502
740
|
}
|
|
503
|
-
/**
|
|
504
|
-
|
|
741
|
+
/**
|
|
742
|
+
* The editor for a document that does.
|
|
743
|
+
*
|
|
744
|
+
* The stored instant is shown as the clock in the site's zone reads it, and
|
|
745
|
+
* {@link wallClockIn} keeps whatever precision the instant has, so a form
|
|
746
|
+
* submitted with the field untouched writes back the very instant it was
|
|
747
|
+
* filled in from.
|
|
748
|
+
*/
|
|
749
|
+
export function formFor(document, timezone = DEFAULT_TIMEZONE) {
|
|
505
750
|
return {
|
|
506
751
|
title: document.title,
|
|
507
752
|
slug: document.slug,
|
|
508
753
|
permalink: document.permalink,
|
|
509
|
-
date: document.date
|
|
754
|
+
date: document.date === undefined ? '' : wallClockIn(document.date, timezone),
|
|
510
755
|
tags: document.tags.join(', '),
|
|
756
|
+
categories: document.categories.join(', '),
|
|
511
757
|
description: document.description ?? '',
|
|
758
|
+
author: document.author ?? '',
|
|
512
759
|
draft: document.draft,
|
|
513
760
|
exclude: document.extra[EXCLUDE_KEY] === true,
|
|
761
|
+
contact: document.extra[CONTACT_FRONT_MATTER_KEY] === true,
|
|
762
|
+
comments: commentSettingOf(document),
|
|
514
763
|
body: document.body,
|
|
515
764
|
hash: document.hash,
|
|
516
765
|
};
|
|
@@ -526,6 +775,11 @@ export function formFor(document) {
|
|
|
526
775
|
function renderEditor(c, options) {
|
|
527
776
|
const { kind, document, form } = options;
|
|
528
777
|
const trashed = document !== undefined && isTrashedPath(document.path);
|
|
778
|
+
const now = c.var.store.now();
|
|
779
|
+
const timezone = siteTimezone(c);
|
|
780
|
+
// Printed in the site's own time zone rather than in UTC: an author who
|
|
781
|
+
// scheduled a post for nine in the morning meant their own morning.
|
|
782
|
+
const scheduledAt = document === undefined || trashed || document.draft ? undefined : scheduledFor(document, now);
|
|
529
783
|
const actions = [];
|
|
530
784
|
if (document === undefined || document.draft) {
|
|
531
785
|
actions.push({ value: 'save-draft', label: 'Save draft' });
|
|
@@ -542,16 +796,26 @@ function renderEditor(c, options) {
|
|
|
542
796
|
c.status(options.status);
|
|
543
797
|
return options.render(c, ADMIN_TEMPLATES.documentEditor, {
|
|
544
798
|
section: kind.section,
|
|
799
|
+
// The blank form is Add new; editing one that exists is still All posts,
|
|
800
|
+
// the way WordPress leaves the listing marked while you are in the editor.
|
|
801
|
+
child: document === undefined ? 'new' : 'all',
|
|
545
802
|
kind,
|
|
546
803
|
form,
|
|
547
804
|
actions,
|
|
548
805
|
trashed,
|
|
806
|
+
commentSettings: COMMENT_SETTINGS,
|
|
807
|
+
// Who this can be attributed to, and who it is attributed to now.
|
|
808
|
+
authors: authorChoices(c, form.author),
|
|
549
809
|
heading: document === undefined ? `Add ${kind.singular}` : `Edit ${kind.singular}: ${document.title}`,
|
|
550
810
|
saveUrl: document === undefined ? newEditorPath(kind) : editorPath(kind, document.slug),
|
|
551
811
|
listUrl: kind.basePath,
|
|
552
812
|
previewUrl: PREVIEW_PATH,
|
|
553
813
|
uploadUrl: UPLOADS_PATH,
|
|
554
|
-
viewUrl: document !== undefined && isPublicDocument(document) ? document.permalink : undefined,
|
|
814
|
+
viewUrl: document !== undefined && isPublicDocument(document, now) ? document.permalink : undefined,
|
|
815
|
+
// Named beside the date field, because a wall clock with no zone on it is
|
|
816
|
+
// exactly the ambiguity decision-11 exists to remove.
|
|
817
|
+
...(kind.dated ? { dateZone: zoneLabel(form.date === '' ? now : form.date, timezone) } : {}),
|
|
818
|
+
...(scheduledAt === undefined ? {} : { scheduledFor: formatInTimezone(scheduledAt, timezone) }),
|
|
555
819
|
...(options.error === undefined ? {} : { error: options.error }),
|
|
556
820
|
});
|
|
557
821
|
}
|
|
@@ -576,13 +840,33 @@ export function findBySlug(store, kind, slug) {
|
|
|
576
840
|
}
|
|
577
841
|
return undefined;
|
|
578
842
|
}
|
|
579
|
-
|
|
580
|
-
|
|
843
|
+
/**
|
|
844
|
+
* WordPress's own two labels for the pages the Reading setting names, beside
|
|
845
|
+
* the title on the pages screen, so it is plain from the list which page is
|
|
846
|
+
* the front page and which carries the posts.
|
|
847
|
+
*/
|
|
848
|
+
export const PAGE_ROLE_LABELS = { homepage: 'Front Page', postsPage: 'Posts Page' };
|
|
849
|
+
/** What one row's page is to the site, if anything. */
|
|
850
|
+
function pageRole(kind, document, c) {
|
|
851
|
+
if (kind.type !== 'page')
|
|
852
|
+
return undefined;
|
|
853
|
+
const settings = readSiteSettings(c.var.config.contentDir);
|
|
854
|
+
if (settings.homepage === '')
|
|
855
|
+
return undefined;
|
|
856
|
+
if (document.slug === settings.homepage)
|
|
857
|
+
return PAGE_ROLE_LABELS.homepage;
|
|
858
|
+
if (document.slug === settings.postsPage)
|
|
859
|
+
return PAGE_ROLE_LABELS.postsPage;
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|
|
862
|
+
function listRow(kind, document, now, role) {
|
|
863
|
+
const isPublic = isPublicDocument(document, now);
|
|
581
864
|
return {
|
|
582
865
|
title: document.title,
|
|
583
866
|
slug: document.slug,
|
|
584
867
|
author: document.author,
|
|
585
868
|
tags: document.tags.join(', '),
|
|
869
|
+
categories: document.categories.join(', '),
|
|
586
870
|
date: document.date,
|
|
587
871
|
// Both come out of the index, so the listing costs no reads of its own.
|
|
588
872
|
// A page that has never been saved through the admin has no `updated`, and
|
|
@@ -590,13 +874,16 @@ function listRow(kind, document) {
|
|
|
590
874
|
updated: document.updated ?? document.date,
|
|
591
875
|
draft: document.draft,
|
|
592
876
|
trashed: isTrashedPath(document.path),
|
|
877
|
+
scheduled: scheduledFor(document, now) !== undefined,
|
|
593
878
|
editUrl: editorPath(kind, document.slug),
|
|
594
879
|
viewUrl: isPublic ? document.permalink : undefined,
|
|
880
|
+
role,
|
|
595
881
|
};
|
|
596
882
|
}
|
|
597
883
|
const FILTER_LABELS = {
|
|
598
884
|
all: 'All',
|
|
599
885
|
published: 'Published',
|
|
886
|
+
scheduled: 'Scheduled',
|
|
600
887
|
draft: 'Drafts',
|
|
601
888
|
trash: 'Trash',
|
|
602
889
|
};
|