@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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { FORM_LOADED_FIELD, FORM_TRAP_FIELD } from '../forms/protection.js';
|
|
2
|
+
/**
|
|
3
|
+
* The contact form: what it is called, what it carries, and when a page has
|
|
4
|
+
* one at all.
|
|
5
|
+
*
|
|
6
|
+
* A page opts in with `contact: true` in its front matter — one key, ignored by
|
|
7
|
+
* Eleventy, the same shape as `navigation: true` — and the theme renders
|
|
8
|
+
* `partials/contact-form.njk` under the page's content. Nothing about where the
|
|
9
|
+
* message goes is here or anywhere the template can reach: the address is read
|
|
10
|
+
* when a submission arrives, so it cannot appear in the HTML however a theme is
|
|
11
|
+
* written.
|
|
12
|
+
*/
|
|
13
|
+
/** The front matter key a page opts in with. */
|
|
14
|
+
export const CONTACT_FRONT_MATTER_KEY = 'contact';
|
|
15
|
+
/** Where the contact form posts, under the CMS's own prefix. */
|
|
16
|
+
export const CONTACT_POST_PATH = '/_geekity/contact';
|
|
17
|
+
/** The query the redirect after a submission carries. */
|
|
18
|
+
export const CONTACT_NOTICE_PARAM = 'contact';
|
|
19
|
+
/** The anchor the form and its thank-you sit at, so a redirect lands on them. */
|
|
20
|
+
export const CONTACT_ANCHOR = 'contact';
|
|
21
|
+
/**
|
|
22
|
+
* What that query means, and what the sender is told.
|
|
23
|
+
*
|
|
24
|
+
* A redirect rather than a rendered page, so a refresh does not send the
|
|
25
|
+
* message a second time; a query rather than a flash, because a visitor has no
|
|
26
|
+
* session to hang one on.
|
|
27
|
+
*/
|
|
28
|
+
export const CONTACT_NOTICES = {
|
|
29
|
+
sent: 'Thank you — your message has been sent.',
|
|
30
|
+
};
|
|
31
|
+
/** The fields the contact form submits. */
|
|
32
|
+
export const CONTACT_FIELDS = {
|
|
33
|
+
/** Which page the form was on, as its slug. */
|
|
34
|
+
page: 'page',
|
|
35
|
+
/** The sender's name. Required. */
|
|
36
|
+
name: 'name',
|
|
37
|
+
/** Their email, which the message replies to. Required. */
|
|
38
|
+
email: 'email',
|
|
39
|
+
/** What it is about. Optional. */
|
|
40
|
+
subject: 'subject',
|
|
41
|
+
/** The message itself, as plain text. Required. */
|
|
42
|
+
message: 'message',
|
|
43
|
+
/** The honeypot, which every public form here spells the same way. */
|
|
44
|
+
trap: FORM_TRAP_FIELD,
|
|
45
|
+
/** When the form was rendered, in epoch milliseconds. */
|
|
46
|
+
loaded: FORM_LOADED_FIELD,
|
|
47
|
+
};
|
|
48
|
+
/** The longest a name may be. */
|
|
49
|
+
export const MAXIMUM_CONTACT_NAME_LENGTH = 80;
|
|
50
|
+
/** The longest an email address may be. */
|
|
51
|
+
export const MAXIMUM_CONTACT_EMAIL_LENGTH = 500;
|
|
52
|
+
/** The longest a subject may be. */
|
|
53
|
+
export const MAXIMUM_CONTACT_SUBJECT_LENGTH = 200;
|
|
54
|
+
/** The longest a message may be, in characters. */
|
|
55
|
+
export const MAXIMUM_CONTACT_MESSAGE_LENGTH = 10_000;
|
|
56
|
+
/** Whether this document offers a contact form. */
|
|
57
|
+
export function contactOpen(document) {
|
|
58
|
+
return document.extra[CONTACT_FRONT_MATTER_KEY] === true;
|
|
59
|
+
}
|
|
60
|
+
/** A fresh, empty form for a page that asked for one. */
|
|
61
|
+
export function contactForm(document, now = new Date()) {
|
|
62
|
+
return refilledContactForm(document, blankContactValues(), {}, now);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The same form with what somebody typed still in it.
|
|
66
|
+
*
|
|
67
|
+
* What a refused submission is answered with: losing a paragraph because an
|
|
68
|
+
* email address had a typo in it is the fastest way to lose a message.
|
|
69
|
+
*/
|
|
70
|
+
export function refilledContactForm(document, values, problems, now = new Date(), error) {
|
|
71
|
+
return {
|
|
72
|
+
action: CONTACT_POST_PATH,
|
|
73
|
+
fields: CONTACT_FIELDS,
|
|
74
|
+
page: document.slug,
|
|
75
|
+
loaded: String(now.getTime()),
|
|
76
|
+
values,
|
|
77
|
+
problems,
|
|
78
|
+
nameLength: MAXIMUM_CONTACT_NAME_LENGTH,
|
|
79
|
+
subjectLength: MAXIMUM_CONTACT_SUBJECT_LENGTH,
|
|
80
|
+
messageLength: MAXIMUM_CONTACT_MESSAGE_LENGTH,
|
|
81
|
+
...(error === undefined ? {} : { error }),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** What a form holds before anybody has typed in it. */
|
|
85
|
+
export function blankContactValues() {
|
|
86
|
+
return { name: '', email: '', subject: '', message: '' };
|
|
87
|
+
}
|
|
88
|
+
/** What a submitted form typed, for putting back in a refused one. */
|
|
89
|
+
export function contactValuesOf(form) {
|
|
90
|
+
return {
|
|
91
|
+
name: form.name,
|
|
92
|
+
email: form.email,
|
|
93
|
+
subject: form.subject,
|
|
94
|
+
message: form.message,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The form for a document, or `undefined` when it does not offer one.
|
|
99
|
+
*
|
|
100
|
+
* What the renderer is handed at boot, and asked per render: a `contact: true`
|
|
101
|
+
* added to a page in the editor puts a form on it on the very next request.
|
|
102
|
+
*/
|
|
103
|
+
export function contactFormFor(options) {
|
|
104
|
+
if (!contactOpen(options.document))
|
|
105
|
+
return undefined;
|
|
106
|
+
return contactForm(options.document, options.now);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The thank-you a sender is shown after a submission, from the query the
|
|
110
|
+
* redirect carried, or `undefined` when the URL says nothing.
|
|
111
|
+
*
|
|
112
|
+
* An unknown value says nothing at all rather than being printed, so the query
|
|
113
|
+
* cannot be used to put words on somebody's page.
|
|
114
|
+
*/
|
|
115
|
+
export function contactNoticeFor(query) {
|
|
116
|
+
return query === undefined ? undefined : CONTACT_NOTICES[query];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* What is wrong with a submitted form, as a person would be told it.
|
|
120
|
+
*
|
|
121
|
+
* Only the things a person can fix. The honeypot, the form's age and the rate
|
|
122
|
+
* limit are not here: none of them is the sender's mistake, and two of them
|
|
123
|
+
* should not be explained to whoever tripped them.
|
|
124
|
+
*/
|
|
125
|
+
export function contactProblems(form) {
|
|
126
|
+
const problems = {};
|
|
127
|
+
const name = form.name.trim();
|
|
128
|
+
if (name === '')
|
|
129
|
+
problems.name = 'A message needs a name to come from.';
|
|
130
|
+
else if (name.length > MAXIMUM_CONTACT_NAME_LENGTH) {
|
|
131
|
+
problems.name = `That name is longer than ${String(MAXIMUM_CONTACT_NAME_LENGTH)} characters.`;
|
|
132
|
+
}
|
|
133
|
+
// Required, unlike a comment's, because the whole message is a request for
|
|
134
|
+
// an answer: a reply-to is what the site is being asked for.
|
|
135
|
+
const email = form.email.trim();
|
|
136
|
+
if (email === '')
|
|
137
|
+
problems.email = 'A message needs an address to reply to.';
|
|
138
|
+
else if (!isEmail(email))
|
|
139
|
+
problems.email = 'That does not look like an email address.';
|
|
140
|
+
if (form.subject.trim().length > MAXIMUM_CONTACT_SUBJECT_LENGTH) {
|
|
141
|
+
problems.subject = `That subject is longer than ${String(MAXIMUM_CONTACT_SUBJECT_LENGTH)} characters.`;
|
|
142
|
+
}
|
|
143
|
+
const message = form.message.trim();
|
|
144
|
+
if (message === '')
|
|
145
|
+
problems.message = 'A message needs something in it.';
|
|
146
|
+
else if (message.length > MAXIMUM_CONTACT_MESSAGE_LENGTH) {
|
|
147
|
+
problems.message = `That message is longer than ${String(MAXIMUM_CONTACT_MESSAGE_LENGTH)} characters.`;
|
|
148
|
+
}
|
|
149
|
+
return problems;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Whether something looks like an email address.
|
|
153
|
+
*
|
|
154
|
+
* Deliberately loose, and the same rule the comment form uses: the only thing
|
|
155
|
+
* a stricter one would buy is refusing valid addresses. Whether it can be
|
|
156
|
+
* delivered to is a question the provider answers.
|
|
157
|
+
*/
|
|
158
|
+
function isEmail(value) {
|
|
159
|
+
if (value.length > MAXIMUM_CONTACT_EMAIL_LENGTH)
|
|
160
|
+
return false;
|
|
161
|
+
return /^[^\s@]+@[^\s@.]+(\.[^\s@.]+)+$/.test(value);
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/contact/form.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contact form (TASK-56): a page that offers one, the defences in front of
|
|
3
|
+
* it, the file every message lands in, and the mail that carries it.
|
|
4
|
+
*
|
|
5
|
+
* The barrel every other module and the package index import from, so nothing
|
|
6
|
+
* outside here has to know which file a name lives in.
|
|
7
|
+
*/
|
|
8
|
+
export { blankContactValues, CONTACT_ANCHOR, CONTACT_FIELDS, CONTACT_FRONT_MATTER_KEY, CONTACT_NOTICE_PARAM, CONTACT_NOTICES, CONTACT_POST_PATH, contactForm, contactFormFor, contactNoticeFor, contactOpen, contactProblems, contactValuesOf, MAXIMUM_CONTACT_EMAIL_LENGTH, MAXIMUM_CONTACT_MESSAGE_LENGTH, MAXIMUM_CONTACT_NAME_LENGTH, MAXIMUM_CONTACT_SUBJECT_LENGTH, refilledContactForm, } from './form.ts';
|
|
9
|
+
export type { ContactForm, ContactFormContext, ContactProblems } from './form.ts';
|
|
10
|
+
export { addContactMessage, CONTACT_DATA_DIRECTORY, contactDirectory, contactMessageFile, contactMessageId, countContactMessagesByStatus, countUnreadContactMessages, deleteContactMessage, listContactMessages, readContactMessage, setContactMessageRead, } from './records.ts';
|
|
11
|
+
export type { ContactMessage, ContactStatus, NewContactMessage } from './records.ts';
|
|
12
|
+
export { CONTACT_RATE_LIMIT, CONTACT_RATE_WINDOW_SECONDS, contactKeys, submitContactMessage, } from './submission.ts';
|
|
13
|
+
export type { ContactOutcome, ContactRefusal, ContactThrottle, SubmitContactMessageOptions, } from './submission.ts';
|
|
14
|
+
export { CONTACT_MESSAGE_TEMPLATE, contactRecipient, sendContactMessage } from './delivery.ts';
|
|
15
|
+
export type { ContactRecipientOptions, SendContactMessageOptions } from './delivery.ts';
|
|
16
|
+
export { mountContact } from './routes.ts';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contact/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,eAAe,EACf,4BAA4B,EAC5B,8BAA8B,EAC9B,2BAA2B,EAC3B,8BAA8B,EAC9B,mBAAmB,GACpB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAElF,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,4BAA4B,EAC5B,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,WAAW,EACX,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC/F,YAAY,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAExF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contact form (TASK-56): a page that offers one, the defences in front of
|
|
3
|
+
* it, the file every message lands in, and the mail that carries it.
|
|
4
|
+
*
|
|
5
|
+
* The barrel every other module and the package index import from, so nothing
|
|
6
|
+
* outside here has to know which file a name lives in.
|
|
7
|
+
*/
|
|
8
|
+
export { blankContactValues, CONTACT_ANCHOR, CONTACT_FIELDS, CONTACT_FRONT_MATTER_KEY, CONTACT_NOTICE_PARAM, CONTACT_NOTICES, CONTACT_POST_PATH, contactForm, contactFormFor, contactNoticeFor, contactOpen, contactProblems, contactValuesOf, MAXIMUM_CONTACT_EMAIL_LENGTH, MAXIMUM_CONTACT_MESSAGE_LENGTH, MAXIMUM_CONTACT_NAME_LENGTH, MAXIMUM_CONTACT_SUBJECT_LENGTH, refilledContactForm, } from './form.js';
|
|
9
|
+
export { addContactMessage, CONTACT_DATA_DIRECTORY, contactDirectory, contactMessageFile, contactMessageId, countContactMessagesByStatus, countUnreadContactMessages, deleteContactMessage, listContactMessages, readContactMessage, setContactMessageRead, } from './records.js';
|
|
10
|
+
export { CONTACT_RATE_LIMIT, CONTACT_RATE_WINDOW_SECONDS, contactKeys, submitContactMessage, } from './submission.js';
|
|
11
|
+
export { CONTACT_MESSAGE_TEMPLATE, contactRecipient, sendContactMessage } from './delivery.js';
|
|
12
|
+
export { mountContact } from './routes.js';
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contact/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,cAAc,EACd,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,eAAe,EACf,4BAA4B,EAC5B,8BAA8B,EAC9B,2BAA2B,EAC3B,8BAA8B,EAC9B,mBAAmB,GACpB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,4BAA4B,EAC5B,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,kBAAkB,EAClB,2BAA2B,EAC3B,WAAW,EACX,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAG/F,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A contact message as the file that holds it.
|
|
3
|
+
*
|
|
4
|
+
* decision-9 makes the filesystem the source of truth for everything durable,
|
|
5
|
+
* and a message somebody typed into this site is exactly that: nothing else
|
|
6
|
+
* holds it, and no other server can be asked for it again. So it is written to
|
|
7
|
+
* disk **before** anything is emailed, which is the point — a provider that is
|
|
8
|
+
* down, a key that has expired, an address that bounces, and the message is
|
|
9
|
+
* still on the Messages screen tomorrow.
|
|
10
|
+
*
|
|
11
|
+
* Two things separate this from the comment files:
|
|
12
|
+
*
|
|
13
|
+
* - **It lives under `data/`, not under `content/`.** A message carries the
|
|
14
|
+
* sender's email address and was never meant to be published; `content/` is
|
|
15
|
+
* in git and is copied to the public site by an Eleventy build.
|
|
16
|
+
* - **There is no SQLite index.** A contact inbox is tens or hundreds of
|
|
17
|
+
* files, the screen reads all of them to sort them anyway, and an index
|
|
18
|
+
* would be a second thing to keep true for no query it makes faster. Files
|
|
19
|
+
* are truth here with nothing derived from them at all.
|
|
20
|
+
*
|
|
21
|
+
* One JSON file per message, named by its id, which starts with the instant it
|
|
22
|
+
* arrived — so `ls` is the inbox in order, and so no two messages can collide.
|
|
23
|
+
*/
|
|
24
|
+
/** Where the messages live, relative to the data directory. */
|
|
25
|
+
export declare const CONTACT_DATA_DIRECTORY = "contact";
|
|
26
|
+
/** The absolute path of one site's `data/contact`. */
|
|
27
|
+
export declare function contactDirectory(dataDir: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* What became of a submission, as the file records it.
|
|
30
|
+
*
|
|
31
|
+
* `spam` is a checker's opinion rather than a person's, and the message is
|
|
32
|
+
* kept and shown on a list of its own rather than thrown away: a false
|
|
33
|
+
* positive on a contact form is a customer whose message vanished, which is a
|
|
34
|
+
* worse failure than a spam list somebody has to glance at. Nothing else is
|
|
35
|
+
* ever written — a submission the honeypot, the clock or the rate limit
|
|
36
|
+
* refused leaves no file at all, because storing those would hand an anonymous
|
|
37
|
+
* caller a way to fill the disk.
|
|
38
|
+
*/
|
|
39
|
+
export type ContactStatus = 'received' | 'spam';
|
|
40
|
+
/** One contact message, as its file says it. */
|
|
41
|
+
export interface ContactMessage {
|
|
42
|
+
/** Its id, which is its filename and starts with the instant it arrived. */
|
|
43
|
+
id: string;
|
|
44
|
+
/** When it arrived, as a UTC instant. */
|
|
45
|
+
received: string;
|
|
46
|
+
/** Whether a checker thought it was spam. */
|
|
47
|
+
status: ContactStatus;
|
|
48
|
+
/** Whether somebody has read it on the Messages screen. */
|
|
49
|
+
read: boolean;
|
|
50
|
+
/** The page the form was on. */
|
|
51
|
+
page: {
|
|
52
|
+
/** Its slug. */
|
|
53
|
+
slug: string;
|
|
54
|
+
/** Its permalink, so the screen can link to where the form was. */
|
|
55
|
+
permalink: string;
|
|
56
|
+
/** Its title at the time, so a page since renamed still reads sensibly. */
|
|
57
|
+
title: string;
|
|
58
|
+
};
|
|
59
|
+
/** Who sent it. */
|
|
60
|
+
from: {
|
|
61
|
+
/** The name they gave. */
|
|
62
|
+
name: string;
|
|
63
|
+
/** The address they gave, which the message replies to. */
|
|
64
|
+
email: string;
|
|
65
|
+
};
|
|
66
|
+
/** What it is about. May be empty. */
|
|
67
|
+
subject: string;
|
|
68
|
+
/** The message itself, as plain text. */
|
|
69
|
+
message: string;
|
|
70
|
+
/**
|
|
71
|
+
* A salted hash of the address it came from, or `null` when the site could
|
|
72
|
+
* not tell. The same hash a comment keeps, so a run from one machine is
|
|
73
|
+
* visible across both.
|
|
74
|
+
*/
|
|
75
|
+
addressHash: string | null;
|
|
76
|
+
}
|
|
77
|
+
/** A message on its way in, before it has been given an id. */
|
|
78
|
+
export type NewContactMessage = Omit<ContactMessage, 'id' | 'read'> & {
|
|
79
|
+
/** Whether it has been read. Defaults to no, which is what a new one is. */
|
|
80
|
+
read?: boolean | undefined;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The id a message received at this instant gets.
|
|
84
|
+
*
|
|
85
|
+
* The instant first, compacted so it is one filename-safe token, and then
|
|
86
|
+
* enough randomness that two messages in the same millisecond cannot collide.
|
|
87
|
+
*/
|
|
88
|
+
export declare function contactMessageId(received: Date): string;
|
|
89
|
+
/** The absolute path of one message's file. */
|
|
90
|
+
export declare function contactMessageFile(dataDir: string, id: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* Write a message, and hand back what was written.
|
|
93
|
+
*
|
|
94
|
+
* The id is minted here rather than by the caller, so the only thing that
|
|
95
|
+
* decides what a message is called is the thing that stores it.
|
|
96
|
+
*/
|
|
97
|
+
export declare function addContactMessage(dataDir: string, message: NewContactMessage): Promise<ContactMessage>;
|
|
98
|
+
/**
|
|
99
|
+
* Every message the directory holds, newest first.
|
|
100
|
+
*
|
|
101
|
+
* A file that will not parse is skipped rather than thrown over: a directory
|
|
102
|
+
* somebody has been editing by hand should cost that message rather than take
|
|
103
|
+
* the Messages screen down, and the file is still there to be looked at.
|
|
104
|
+
*/
|
|
105
|
+
export declare function listContactMessages(dataDir: string, options?: {
|
|
106
|
+
status?: ContactStatus | undefined;
|
|
107
|
+
}): ContactMessage[];
|
|
108
|
+
/** How many messages are waiting to be read, which is the number on the dashboard. */
|
|
109
|
+
export declare function countUnreadContactMessages(dataDir: string): number;
|
|
110
|
+
/** How many messages each list holds, for the tabs on the Messages screen. */
|
|
111
|
+
export declare function countContactMessagesByStatus(dataDir: string): Record<ContactStatus, number>;
|
|
112
|
+
/** One message, or `undefined` for an id nothing is filed under. */
|
|
113
|
+
export declare function readContactMessage(dataDir: string, id: string): ContactMessage | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Mark a message read, or unread again, and hand back what it now says.
|
|
116
|
+
*
|
|
117
|
+
* The file is re-read inside the write, so two admins with the screen open
|
|
118
|
+
* cannot make one of them undo the other's delete by writing back a message
|
|
119
|
+
* that is not there any more.
|
|
120
|
+
*/
|
|
121
|
+
export declare function setContactMessageRead(dataDir: string, id: string, read: boolean): Promise<ContactMessage | undefined>;
|
|
122
|
+
/** Delete a message. `false` when there was nothing of that id to delete. */
|
|
123
|
+
export declare function deleteContactMessage(dataDir: string, id: string): Promise<boolean>;
|
|
124
|
+
//# sourceMappingURL=records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/contact/records.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,YAAY,CAAC;AAEhD,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,MAAM,CAAC;AAEhD,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,EAAE,EAAE,MAAM,CAAC;IACX,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,MAAM,EAAE,aAAa,CAAC;IACtB,2DAA2D;IAC3D,IAAI,EAAE,OAAO,CAAC;IACd,gCAAgC;IAChC,IAAI,EAAE;QACJ,gBAAgB;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,mEAAmE;QACnE,SAAS,EAAE,MAAM,CAAC;QAClB,2EAA2E;QAC3E,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,mBAAmB;IACnB,IAAI,EAAE;QACJ,0BAA0B;QAC1B,IAAI,EAAE,MAAM,CAAC;QACb,2DAA2D;QAC3D,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,MAAM,CAAC,GAAG;IACpE,4EAA4E;IAC5E,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC5B,CAAC;AAWF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,IAAI,GAAG,MAAM,CAGvD;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAKtE;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,cAAc,CAAC,CAgBzB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;CAAO,GACnD,cAAc,EAAE,CAalB;AAED,sFAAsF;AACtF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,8EAA8E;AAC9E,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAI3F;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAO1F;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAmBrC;AAED,6EAA6E;AAC7E,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASxF"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { readdirSync } from 'node:fs';
|
|
3
|
+
import { rm } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { readFileIfPresentSync, withFileLock, writeFileAtomically, writeFileAtomicallySync, } from '../files/atomic.js';
|
|
6
|
+
/**
|
|
7
|
+
* A contact message as the file that holds it.
|
|
8
|
+
*
|
|
9
|
+
* decision-9 makes the filesystem the source of truth for everything durable,
|
|
10
|
+
* and a message somebody typed into this site is exactly that: nothing else
|
|
11
|
+
* holds it, and no other server can be asked for it again. So it is written to
|
|
12
|
+
* disk **before** anything is emailed, which is the point — a provider that is
|
|
13
|
+
* down, a key that has expired, an address that bounces, and the message is
|
|
14
|
+
* still on the Messages screen tomorrow.
|
|
15
|
+
*
|
|
16
|
+
* Two things separate this from the comment files:
|
|
17
|
+
*
|
|
18
|
+
* - **It lives under `data/`, not under `content/`.** A message carries the
|
|
19
|
+
* sender's email address and was never meant to be published; `content/` is
|
|
20
|
+
* in git and is copied to the public site by an Eleventy build.
|
|
21
|
+
* - **There is no SQLite index.** A contact inbox is tens or hundreds of
|
|
22
|
+
* files, the screen reads all of them to sort them anyway, and an index
|
|
23
|
+
* would be a second thing to keep true for no query it makes faster. Files
|
|
24
|
+
* are truth here with nothing derived from them at all.
|
|
25
|
+
*
|
|
26
|
+
* One JSON file per message, named by its id, which starts with the instant it
|
|
27
|
+
* arrived — so `ls` is the inbox in order, and so no two messages can collide.
|
|
28
|
+
*/
|
|
29
|
+
/** Where the messages live, relative to the data directory. */
|
|
30
|
+
export const CONTACT_DATA_DIRECTORY = 'contact';
|
|
31
|
+
/** The absolute path of one site's `data/contact`. */
|
|
32
|
+
export function contactDirectory(dataDir) {
|
|
33
|
+
return path.join(dataDir, CONTACT_DATA_DIRECTORY);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Ids a file may be named after: the instant, a dash and eight hex characters.
|
|
37
|
+
*
|
|
38
|
+
* Checked on the way in as well as on the way out, so an id off a form can
|
|
39
|
+
* never name a path — there is no separator, no dot and no letter outside the
|
|
40
|
+
* set in anything that matches.
|
|
41
|
+
*/
|
|
42
|
+
const SAFE_ID = /^[0-9]{8}T[0-9]{9}Z-[0-9a-f]{8}$/;
|
|
43
|
+
/**
|
|
44
|
+
* The id a message received at this instant gets.
|
|
45
|
+
*
|
|
46
|
+
* The instant first, compacted so it is one filename-safe token, and then
|
|
47
|
+
* enough randomness that two messages in the same millisecond cannot collide.
|
|
48
|
+
*/
|
|
49
|
+
export function contactMessageId(received) {
|
|
50
|
+
const stamp = received.toISOString().replace(/[-:.]/g, '');
|
|
51
|
+
return `${stamp}-${randomBytes(4).toString('hex')}`;
|
|
52
|
+
}
|
|
53
|
+
/** The absolute path of one message's file. */
|
|
54
|
+
export function contactMessageFile(dataDir, id) {
|
|
55
|
+
if (!SAFE_ID.test(id)) {
|
|
56
|
+
throw new Error(`"${id}" is not an id a contact message file can be named after.`);
|
|
57
|
+
}
|
|
58
|
+
return path.join(contactDirectory(dataDir), `${id}.json`);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Write a message, and hand back what was written.
|
|
62
|
+
*
|
|
63
|
+
* The id is minted here rather than by the caller, so the only thing that
|
|
64
|
+
* decides what a message is called is the thing that stores it.
|
|
65
|
+
*/
|
|
66
|
+
export async function addContactMessage(dataDir, message) {
|
|
67
|
+
const stored = {
|
|
68
|
+
...message,
|
|
69
|
+
id: contactMessageId(new Date(message.received)),
|
|
70
|
+
read: message.read ?? false,
|
|
71
|
+
};
|
|
72
|
+
await writeFileAtomically(contactMessageFile(dataDir, stored.id), `${JSON.stringify(stored, null, 2)}\n`,
|
|
73
|
+
// The sender's address is in here, so the file is the site's own to read
|
|
74
|
+
// and nobody else's, exactly like the credentials beside it.
|
|
75
|
+
{ mode: 0o600 });
|
|
76
|
+
return stored;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Every message the directory holds, newest first.
|
|
80
|
+
*
|
|
81
|
+
* A file that will not parse is skipped rather than thrown over: a directory
|
|
82
|
+
* somebody has been editing by hand should cost that message rather than take
|
|
83
|
+
* the Messages screen down, and the file is still there to be looked at.
|
|
84
|
+
*/
|
|
85
|
+
export function listContactMessages(dataDir, options = {}) {
|
|
86
|
+
const messages = [];
|
|
87
|
+
for (const id of contactMessageIds(dataDir)) {
|
|
88
|
+
const message = readContactMessage(dataDir, id);
|
|
89
|
+
if (message === undefined)
|
|
90
|
+
continue;
|
|
91
|
+
if (options.status !== undefined && message.status !== options.status)
|
|
92
|
+
continue;
|
|
93
|
+
messages.push(message);
|
|
94
|
+
}
|
|
95
|
+
// By id, which begins with the instant, so the order is chronological
|
|
96
|
+
// without a date having to be parsed for every comparison.
|
|
97
|
+
return messages.sort((one, other) => (one.id < other.id ? 1 : one.id > other.id ? -1 : 0));
|
|
98
|
+
}
|
|
99
|
+
/** How many messages are waiting to be read, which is the number on the dashboard. */
|
|
100
|
+
export function countUnreadContactMessages(dataDir) {
|
|
101
|
+
return listContactMessages(dataDir).filter((message) => !message.read).length;
|
|
102
|
+
}
|
|
103
|
+
/** How many messages each list holds, for the tabs on the Messages screen. */
|
|
104
|
+
export function countContactMessagesByStatus(dataDir) {
|
|
105
|
+
const counts = { received: 0, spam: 0 };
|
|
106
|
+
for (const message of listContactMessages(dataDir))
|
|
107
|
+
counts[message.status] += 1;
|
|
108
|
+
return counts;
|
|
109
|
+
}
|
|
110
|
+
/** One message, or `undefined` for an id nothing is filed under. */
|
|
111
|
+
export function readContactMessage(dataDir, id) {
|
|
112
|
+
if (!SAFE_ID.test(id))
|
|
113
|
+
return undefined;
|
|
114
|
+
const raw = readFileIfPresentSync(contactMessageFile(dataDir, id));
|
|
115
|
+
if (raw === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
return messageFrom(raw, id);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Mark a message read, or unread again, and hand back what it now says.
|
|
121
|
+
*
|
|
122
|
+
* The file is re-read inside the write, so two admins with the screen open
|
|
123
|
+
* cannot make one of them undo the other's delete by writing back a message
|
|
124
|
+
* that is not there any more.
|
|
125
|
+
*/
|
|
126
|
+
export async function setContactMessageRead(dataDir, id, read) {
|
|
127
|
+
if (!SAFE_ID.test(id))
|
|
128
|
+
return undefined;
|
|
129
|
+
const file = contactMessageFile(dataDir, id);
|
|
130
|
+
// The read, the change and the write inside one lock, with the writer that
|
|
131
|
+
// does not queue: a message deleted while this screen was open must not be
|
|
132
|
+
// written back by a Mark read that arrived a moment later.
|
|
133
|
+
return await withFileLock(file, () => {
|
|
134
|
+
const current = readFileIfPresentSync(file);
|
|
135
|
+
if (current === undefined)
|
|
136
|
+
return undefined;
|
|
137
|
+
const message = messageFrom(current, id);
|
|
138
|
+
if (message === undefined)
|
|
139
|
+
return undefined;
|
|
140
|
+
const updated = { ...message, read };
|
|
141
|
+
writeFileAtomicallySync(file, `${JSON.stringify(updated, null, 2)}\n`, { mode: 0o600 });
|
|
142
|
+
return updated;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/** Delete a message. `false` when there was nothing of that id to delete. */
|
|
146
|
+
export async function deleteContactMessage(dataDir, id) {
|
|
147
|
+
if (!SAFE_ID.test(id))
|
|
148
|
+
return false;
|
|
149
|
+
const file = contactMessageFile(dataDir, id);
|
|
150
|
+
return await withFileLock(file, async () => {
|
|
151
|
+
if (readFileIfPresentSync(file) === undefined)
|
|
152
|
+
return false;
|
|
153
|
+
await rm(file, { force: true });
|
|
154
|
+
return true;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/** Every id the directory holds, unsorted. A missing directory is no messages. */
|
|
158
|
+
function contactMessageIds(dataDir) {
|
|
159
|
+
let entries;
|
|
160
|
+
try {
|
|
161
|
+
entries = readdirSync(contactDirectory(dataDir));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
return entries
|
|
167
|
+
.filter((name) => name.endsWith('.json'))
|
|
168
|
+
.map((name) => name.slice(0, -'.json'.length))
|
|
169
|
+
.filter((id) => SAFE_ID.test(id));
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* One file's bytes as the message it names, or `undefined` when they are not
|
|
173
|
+
* one.
|
|
174
|
+
*
|
|
175
|
+
* Read key by key and tolerantly, like every other file this CMS treats as
|
|
176
|
+
* truth: a message missing a field it should have is shown with that field
|
|
177
|
+
* empty rather than taking the screen down.
|
|
178
|
+
*/
|
|
179
|
+
function messageFrom(source, id) {
|
|
180
|
+
let parsed;
|
|
181
|
+
try {
|
|
182
|
+
parsed = JSON.parse(source);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
188
|
+
return undefined;
|
|
189
|
+
const file = parsed;
|
|
190
|
+
const page = record(file['page']);
|
|
191
|
+
const from = record(file['from']);
|
|
192
|
+
const status = file['status'];
|
|
193
|
+
const addressHash = file['addressHash'];
|
|
194
|
+
return {
|
|
195
|
+
id,
|
|
196
|
+
received: text(file['received']),
|
|
197
|
+
status: status === 'spam' ? 'spam' : 'received',
|
|
198
|
+
read: file['read'] === true,
|
|
199
|
+
page: {
|
|
200
|
+
slug: text(page['slug']),
|
|
201
|
+
permalink: text(page['permalink']),
|
|
202
|
+
title: text(page['title']),
|
|
203
|
+
},
|
|
204
|
+
from: { name: text(from['name']), email: text(from['email']) },
|
|
205
|
+
subject: text(file['subject']),
|
|
206
|
+
message: text(file['message']),
|
|
207
|
+
addressHash: typeof addressHash === 'string' && addressHash !== '' ? addressHash : null,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/** One value as a string, or the empty one. */
|
|
211
|
+
function text(value) {
|
|
212
|
+
return typeof value === 'string' ? value : '';
|
|
213
|
+
}
|
|
214
|
+
/** One value as an object, or an empty one. */
|
|
215
|
+
function record(value) {
|
|
216
|
+
return typeof value === 'object' && value !== null ? value : {};
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/contact/records.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import type { GeekityEnv } from '../env.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The one public endpoint the contact form adds: where the form on a page
|
|
5
|
+
* posts to.
|
|
6
|
+
*
|
|
7
|
+
* A single path rather than one per page because the page is a field of the
|
|
8
|
+
* form. That keeps it out of the way of every permalink a site might mint — a
|
|
9
|
+
* path under `/_geekity/` is the CMS's own, like the comment endpoint beside
|
|
10
|
+
* it — and means the route table does not grow with the site.
|
|
11
|
+
*
|
|
12
|
+
* Everything it answers is either a redirect or the page rendered again with
|
|
13
|
+
* the form still filled in. There is no JSON, no fragment and no script.
|
|
14
|
+
*/
|
|
15
|
+
/** Register the contact endpoint. Mounted by the public site. */
|
|
16
|
+
export declare function mountContact(app: Hono<GeekityEnv>): void;
|
|
17
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/contact/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AAK1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAoB5C;;;;;;;;;;;GAWG;AAEH,iEAAiE;AACjE,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CA0FxD"}
|