@geekity/cms 0.13.0 → 0.15.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 +181 -57
- package/admin/pages/documents/editor.njk +82 -0
- package/admin/pages/media/library.njk +4 -3
- package/admin/pages/settings/general.njk +51 -6
- package/admin/static/admin.css +33 -0
- package/dist/admin/documents.d.ts +5 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +63 -6
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/enclosure-field.d.ts +91 -0
- package/dist/admin/enclosure-field.d.ts.map +1 -0
- package/dist/admin/enclosure-field.js +282 -0
- package/dist/admin/enclosure-field.js.map +1 -0
- package/dist/admin/federation.js +2 -2
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/headers.d.ts +4 -1
- package/dist/admin/headers.d.ts.map +1 -1
- package/dist/admin/headers.js +9 -4
- package/dist/admin/headers.js.map +1 -1
- package/dist/admin/media.d.ts +7 -2
- package/dist/admin/media.d.ts.map +1 -1
- package/dist/admin/media.js +4 -2
- package/dist/admin/media.js.map +1 -1
- package/dist/admin/settings-general.d.ts.map +1 -1
- package/dist/admin/settings-general.js +63 -4
- package/dist/admin/settings-general.js.map +1 -1
- package/dist/admin/settings-page.d.ts.map +1 -1
- package/dist/admin/settings-page.js +1 -2
- package/dist/admin/settings-page.js.map +1 -1
- package/dist/admin/settings.d.ts +37 -9
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +93 -11
- package/dist/admin/settings.js.map +1 -1
- package/dist/admin/uploads.d.ts +7 -2
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +23 -5
- package/dist/admin/uploads.js.map +1 -1
- package/dist/config.d.ts +21 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -7
- package/dist/config.js.map +1 -1
- package/dist/content/enclosure.d.ts +69 -0
- package/dist/content/enclosure.d.ts.map +1 -0
- package/dist/content/enclosure.js +165 -0
- package/dist/content/enclosure.js.map +1 -0
- package/dist/content/index.d.ts +3 -1
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +1 -0
- package/dist/content/index.js.map +1 -1
- package/dist/content/media.d.ts +15 -2
- package/dist/content/media.d.ts.map +1 -1
- package/dist/content/media.js +92 -9
- package/dist/content/media.js.map +1 -1
- package/dist/content/pinned.d.ts +26 -0
- package/dist/content/pinned.d.ts.map +1 -0
- package/dist/content/pinned.js +42 -0
- package/dist/content/pinned.js.map +1 -0
- package/dist/content/store.d.ts +7 -0
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +20 -0
- package/dist/content/store.js.map +1 -1
- package/dist/env.d.ts +7 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/federation/actor.d.ts +15 -0
- package/dist/federation/actor.d.ts.map +1 -1
- package/dist/federation/actor.js +94 -3
- package/dist/federation/actor.js.map +1 -1
- package/dist/federation/article.d.ts +13 -1
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +62 -7
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +36 -4
- package/dist/federation/delivery.js.map +1 -1
- package/dist/federation/federation.d.ts.map +1 -1
- package/dist/federation/federation.js +24 -2
- package/dist/federation/federation.js.map +1 -1
- package/dist/federation/mount.js +2 -2
- package/dist/federation/mount.js.map +1 -1
- package/dist/federation/paths.d.ts +30 -0
- package/dist/federation/paths.d.ts.map +1 -1
- package/dist/federation/paths.js +36 -0
- package/dist/federation/paths.js.map +1 -1
- package/dist/images/icons.d.ts +5 -0
- package/dist/images/icons.d.ts.map +1 -1
- package/dist/images/icons.js +13 -8
- package/dist/images/icons.js.map +1 -1
- package/dist/images/paths.d.ts +1 -1
- package/dist/images/paths.d.ts.map +1 -1
- package/dist/images/paths.js.map +1 -1
- package/dist/images/variants.d.ts +11 -0
- package/dist/images/variants.d.ts.map +1 -1
- package/dist/images/variants.js +21 -0
- package/dist/images/variants.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/indieauth/identity.d.ts +3 -3
- package/dist/indieauth/identity.d.ts.map +1 -1
- package/dist/indieauth/identity.js +6 -7
- package/dist/indieauth/identity.js.map +1 -1
- package/dist/web/admin-bar.d.ts +3 -1
- package/dist/web/admin-bar.d.ts.map +1 -1
- package/dist/web/admin-bar.js +5 -1
- package/dist/web/admin-bar.js.map +1 -1
- package/dist/web/assets.d.ts +14 -0
- package/dist/web/assets.d.ts.map +1 -1
- package/dist/web/assets.js +56 -1
- package/dist/web/assets.js.map +1 -1
- package/dist/web/authors.d.ts +19 -0
- package/dist/web/authors.d.ts.map +1 -1
- package/dist/web/authors.js +25 -0
- package/dist/web/authors.js.map +1 -1
- package/dist/web/context.d.ts +26 -0
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +15 -0
- package/dist/web/context.js.map +1 -1
- package/dist/web/feed-atom.d.ts.map +1 -1
- package/dist/web/feed-atom.js +22 -1
- package/dist/web/feed-atom.js.map +1 -1
- package/dist/web/feed-item.d.ts +34 -6
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +31 -7
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-json.d.ts +15 -2
- package/dist/web/feed-json.d.ts.map +1 -1
- package/dist/web/feed-json.js +17 -1
- package/dist/web/feed-json.js.map +1 -1
- package/dist/web/feed-rss.d.ts.map +1 -1
- package/dist/web/feed-rss.js +60 -3
- package/dist/web/feed-rss.js.map +1 -1
- package/dist/web/feed-source.d.ts +3 -0
- package/dist/web/feed-source.d.ts.map +1 -1
- package/dist/web/feed-source.js.map +1 -1
- package/dist/web/feed-xml.d.ts +7 -1
- package/dist/web/feed-xml.d.ts.map +1 -1
- package/dist/web/feed-xml.js +8 -2
- package/dist/web/feed-xml.js.map +1 -1
- package/dist/web/feeds.d.ts +1 -1
- package/dist/web/feeds.d.ts.map +1 -1
- package/dist/web/feeds.js +13 -2
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/index.d.ts +5 -2
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +3 -1
- package/dist/web/index.js.map +1 -1
- package/dist/web/license.d.ts +94 -0
- package/dist/web/license.d.ts.map +1 -0
- package/dist/web/license.js +116 -0
- package/dist/web/license.js.map +1 -0
- package/dist/web/oembed.d.ts +104 -0
- package/dist/web/oembed.d.ts.map +1 -0
- package/dist/web/oembed.js +289 -0
- package/dist/web/oembed.js.map +1 -0
- package/dist/web/opensearch.d.ts +35 -0
- package/dist/web/opensearch.d.ts.map +1 -0
- package/dist/web/opensearch.js +65 -0
- package/dist/web/opensearch.js.map +1 -0
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +16 -8
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +82 -6
- package/dist/web/routes.js.map +1 -1
- package/dist/web/share-image.d.ts +7 -7
- package/dist/web/share-image.d.ts.map +1 -1
- package/dist/web/share-image.js +8 -7
- package/dist/web/share-image.js.map +1 -1
- package/dist/web/templates.d.ts.map +1 -1
- package/dist/web/templates.js +5 -0
- package/dist/web/templates.js.map +1 -1
- package/package.json +2 -1
- package/templates/site/content/_data/site.json +0 -1
- package/themes/default/README.md +142 -48
- package/themes/default/layouts/base.njk +25 -6
- package/themes/default/layouts/front-page.njk +1 -0
- package/themes/default/layouts/home.njk +1 -0
- package/themes/default/layouts/post.njk +4 -0
- package/themes/default/partials/bio.njk +7 -0
- package/themes/default/partials/jsonld.njk +30 -7
- package/themes/default/partials/recording.njk +41 -0
- package/themes/default/src/style.css +16 -0
- package/themes/default/static/style.css +29 -0
package/README.md
CHANGED
|
@@ -1671,53 +1671,53 @@ that ship inside the package, deliberately outside the theme search path: a
|
|
|
1671
1671
|
site's theme may override any public template, and must not be able to
|
|
1672
1672
|
shadow the login form.
|
|
1673
1673
|
|
|
1674
|
-
| Route | What it does
|
|
1675
|
-
| ------------------------------------------------ |
|
|
1676
|
-
| `/admin` | The dashboard: counts, the five most recent posts, the follower count.
|
|
1677
|
-
| `/admin/posts`, `/admin/pages` | The listings and the editors.
|
|
1678
|
-
| `/admin/tags`, `/admin/categories` | Every term in use, with rename, merge and delete.
|
|
1679
|
-
| `/admin/tags/rename`, `/admin/categories/rename` | `POST` only. Renames a term, or merges it into one that exists.
|
|
1680
|
-
| `/admin/tags/delete`, `/admin/categories/delete` | `POST` only. Takes a term out of every file.
|
|
1681
|
-
| `/admin/navigation` | Navigation > Menus: every menu the site holds. `POST` saves one menu's items.
|
|
1682
|
-
| `/admin/navigation/add` | `POST` only. Adds an empty menu under the name the form gives.
|
|
1683
|
-
| `/admin/navigation/delete` | `POST` only. Deletes one menu the active theme renders nowhere.
|
|
1684
|
-
| `/admin/media` | Everything under `content/uploads`, with the URL, the Markdown and what uses it.
|
|
1685
|
-
| `/admin/media/upload` | `POST` only. Stores one file by the rules the editor's upload enforces.
|
|
1686
|
-
| `/admin/media/delete` | `POST` only. Deletes one upload, asking first when a document points at it.
|
|
1687
|
-
| `/admin/media/alt` | `POST` only. Sets a picture's alt text, or marks it decorative, in `media.json`.
|
|
1688
|
-
| `/admin/comments` | Pending, approved and spam, with approve, spam, delete and reply.
|
|
1689
|
-
| `/admin/comments/moderate` | `POST` only. Approves one comment, files it as spam, or deletes it.
|
|
1690
|
-
| `/admin/comments/reply` | `POST` only. Posts an approved reply under the comment it answers.
|
|
1691
|
-
| `/admin/messages` | The contact form's inbox, with a Spam list beside it.
|
|
1692
|
-
| `/admin/messages/read` | `POST` only. Marks one message read, or unread again.
|
|
1693
|
-
| `/admin/messages/delete` | `POST` only. Deletes one message, and its file with it.
|
|
1694
|
-
| `/admin/appearance/themes` | Appearance > Themes: the themes on disk. `POST` activates the one named.
|
|
1695
|
-
| `/admin/tools` | Tools > Content index: what the index holds, and the button that rebuilds it.
|
|
1696
|
-
| `/admin/tools/rebuild-index` | `POST` only. Offers the rebuild, then reads every file again on the live site.
|
|
1697
|
-
| `/admin/settings` | Settings > General: title, tagline, author, base URL, time zone, language, locale.
|
|
1698
|
-
| `/admin/settings/reading` | What the homepage displays, posts per page, the notify server.
|
|
1699
|
-
| `/admin/settings/permalinks` | The tag and category bases, and the archive redirects already recorded.
|
|
1700
|
-
| `/admin/settings/discussion` | Comments and the closing window, webmentions, and the Akismet key.
|
|
1701
|
-
| `/admin/settings/akismet` | `POST` only. Saves the Akismet key, or forgets it.
|
|
1702
|
-
| `/admin/settings/email` | The mail provider, the From line, the reply-to and the contact address.
|
|
1703
|
-
| `/admin/settings/mail` | `POST` only. Saves a mail credential, or forgets every one of them.
|
|
1704
|
-
| `/admin/settings/mail/test` | `POST` only. Sends the theme's test message through the whole chain.
|
|
1705
|
-
| `/admin/users` | Who may sign in: a row each, with Edit and Delete. Nothing on it edits anybody.
|
|
1706
|
-
| `/admin/users/new` | Users > Add new: the add form. `POST` adds one.
|
|
1707
|
-
| `/admin/users/<id>` | One user: the account, the profile, the notices, your password, and the delete.
|
|
1708
|
-
| `/admin/users/password` | `POST` only. Changes the signed-in admin's own password.
|
|
1709
|
-
| `/admin/users/email` | `POST` only. Sets or clears the email address on the user the form names.
|
|
1710
|
-
| `/admin/users/profile` | `POST` only. Saves the whole public profile of the user the form names.
|
|
1711
|
-
| `/admin/users/notifications` | `POST` only. Turns one notice on or off for the user the form names.
|
|
1712
|
-
| `/admin/users/notifications/mode` | `POST` only. Sets how often that notice arrives: as they arrive, hourly or daily.
|
|
1713
|
-
| `/admin/users/delete` | `POST` only. Deletes the user the form names.
|
|
1714
|
-
| `/admin/federation` | The actors, their followers, the inbox log, and per-post delivery.
|
|
1715
|
-
| `/admin/federation/settings` | Federation > Settings: the relays the site subscribes to, and the WordPress switch.
|
|
1716
|
-
| `/admin/federation/resend` | `POST` only. Sends one post to the followers again, as its file now reads.
|
|
1717
|
-
| `/admin/setup` | First run: creates the first admin. Closed once a user exists.
|
|
1718
|
-
| `/admin/login` | Username and password.
|
|
1719
|
-
| `/admin/logout` | `POST` only. Deletes the session row and sends `Clear-Site-Data`.
|
|
1720
|
-
| `/admin/_static/*` | The admin's own stylesheet and scripts, cached for an hour.
|
|
1674
|
+
| Route | What it does |
|
|
1675
|
+
| ------------------------------------------------ | --------------------------------------------------------------------------------------------- |
|
|
1676
|
+
| `/admin` | The dashboard: counts, the five most recent posts, the follower count. |
|
|
1677
|
+
| `/admin/posts`, `/admin/pages` | The listings and the editors. |
|
|
1678
|
+
| `/admin/tags`, `/admin/categories` | Every term in use, with rename, merge and delete. |
|
|
1679
|
+
| `/admin/tags/rename`, `/admin/categories/rename` | `POST` only. Renames a term, or merges it into one that exists. |
|
|
1680
|
+
| `/admin/tags/delete`, `/admin/categories/delete` | `POST` only. Takes a term out of every file. |
|
|
1681
|
+
| `/admin/navigation` | Navigation > Menus: every menu the site holds. `POST` saves one menu's items. |
|
|
1682
|
+
| `/admin/navigation/add` | `POST` only. Adds an empty menu under the name the form gives. |
|
|
1683
|
+
| `/admin/navigation/delete` | `POST` only. Deletes one menu the active theme renders nowhere. |
|
|
1684
|
+
| `/admin/media` | Everything under `content/uploads`, with the URL, the Markdown and what uses it. |
|
|
1685
|
+
| `/admin/media/upload` | `POST` only. Stores one file by the rules the editor's upload enforces. |
|
|
1686
|
+
| `/admin/media/delete` | `POST` only. Deletes one upload, asking first when a document points at it. |
|
|
1687
|
+
| `/admin/media/alt` | `POST` only. Sets a picture's alt text, or marks it decorative, in `media.json`. |
|
|
1688
|
+
| `/admin/comments` | Pending, approved and spam, with approve, spam, delete and reply. |
|
|
1689
|
+
| `/admin/comments/moderate` | `POST` only. Approves one comment, files it as spam, or deletes it. |
|
|
1690
|
+
| `/admin/comments/reply` | `POST` only. Posts an approved reply under the comment it answers. |
|
|
1691
|
+
| `/admin/messages` | The contact form's inbox, with a Spam list beside it. |
|
|
1692
|
+
| `/admin/messages/read` | `POST` only. Marks one message read, or unread again. |
|
|
1693
|
+
| `/admin/messages/delete` | `POST` only. Deletes one message, and its file with it. |
|
|
1694
|
+
| `/admin/appearance/themes` | Appearance > Themes: the themes on disk. `POST` activates the one named. |
|
|
1695
|
+
| `/admin/tools` | Tools > Content index: what the index holds, and the button that rebuilds it. |
|
|
1696
|
+
| `/admin/tools/rebuild-index` | `POST` only. Offers the rebuild, then reads every file again on the live site. |
|
|
1697
|
+
| `/admin/settings` | Settings > General: title, tagline, author, base URL, time zone, language, locale, site icon. |
|
|
1698
|
+
| `/admin/settings/reading` | What the homepage displays, posts per page, the notify server. |
|
|
1699
|
+
| `/admin/settings/permalinks` | The tag and category bases, and the archive redirects already recorded. |
|
|
1700
|
+
| `/admin/settings/discussion` | Comments and the closing window, webmentions, and the Akismet key. |
|
|
1701
|
+
| `/admin/settings/akismet` | `POST` only. Saves the Akismet key, or forgets it. |
|
|
1702
|
+
| `/admin/settings/email` | The mail provider, the From line, the reply-to and the contact address. |
|
|
1703
|
+
| `/admin/settings/mail` | `POST` only. Saves a mail credential, or forgets every one of them. |
|
|
1704
|
+
| `/admin/settings/mail/test` | `POST` only. Sends the theme's test message through the whole chain. |
|
|
1705
|
+
| `/admin/users` | Who may sign in: a row each, with Edit and Delete. Nothing on it edits anybody. |
|
|
1706
|
+
| `/admin/users/new` | Users > Add new: the add form. `POST` adds one. |
|
|
1707
|
+
| `/admin/users/<id>` | One user: the account, the profile, the notices, your password, and the delete. |
|
|
1708
|
+
| `/admin/users/password` | `POST` only. Changes the signed-in admin's own password. |
|
|
1709
|
+
| `/admin/users/email` | `POST` only. Sets or clears the email address on the user the form names. |
|
|
1710
|
+
| `/admin/users/profile` | `POST` only. Saves the whole public profile of the user the form names. |
|
|
1711
|
+
| `/admin/users/notifications` | `POST` only. Turns one notice on or off for the user the form names. |
|
|
1712
|
+
| `/admin/users/notifications/mode` | `POST` only. Sets how often that notice arrives: as they arrive, hourly or daily. |
|
|
1713
|
+
| `/admin/users/delete` | `POST` only. Deletes the user the form names. |
|
|
1714
|
+
| `/admin/federation` | The actors, their followers, the inbox log, and per-post delivery. |
|
|
1715
|
+
| `/admin/federation/settings` | Federation > Settings: the relays the site subscribes to, and the WordPress switch. |
|
|
1716
|
+
| `/admin/federation/resend` | `POST` only. Sends one post to the followers again, as its file now reads. |
|
|
1717
|
+
| `/admin/setup` | First run: creates the first admin. Closed once a user exists. |
|
|
1718
|
+
| `/admin/login` | Username and password. |
|
|
1719
|
+
| `/admin/logout` | `POST` only. Deletes the session row and sends `Clear-Site-Data`. |
|
|
1720
|
+
| `/admin/_static/*` | The admin's own stylesheet and scripts, cached for an hour. |
|
|
1721
1721
|
|
|
1722
1722
|
The screens behind the login share one layout: a bar across the top with the
|
|
1723
1723
|
site name and a link to the public site, the menu down the left, and a place for
|
|
@@ -1844,9 +1844,10 @@ can get between. So a reader — an Eleventy build, the CMS's own site data
|
|
|
1844
1844
|
source, another process entirely — always sees one whole version, and two saves
|
|
1845
1845
|
at once cannot each keep half of what the other kept.
|
|
1846
1846
|
|
|
1847
|
-
The file carries `title`, `tagline`, `url`, `author
|
|
1848
|
-
`homepage`, `postsPage`,
|
|
1849
|
-
`timezone`, `language`, `locale` (when it is set), `
|
|
1847
|
+
The file carries `title`, `tagline`, `url`, `author` (a username, when the
|
|
1848
|
+
site has one author), `postsPerPage`, `homepage`, `postsPage`,
|
|
1849
|
+
`timezone`, `language`, `locale` (when it is set), `icon` (when it is set),
|
|
1850
|
+
`license` and `licenseName` (when a license is chosen; see below), `tagBase`,
|
|
1850
1851
|
`categoryBase`, `notifyServer`, `feedUpdatePeriod`, `feedUpdateFrequency`,
|
|
1851
1852
|
`webmentionsSend`, `webmentionsReceive`,
|
|
1852
1853
|
`mailProvider`, `mailFromName`, `mailFromAddress`, `mailReplyTo`,
|
|
@@ -1999,7 +2000,7 @@ would have done anyway.
|
|
|
1999
2000
|
| `removeImageVariants` | Take a source's derived directory away. |
|
|
2000
2001
|
| `responsiveImages(html, describe)` | Rewrite `<img src="/uploads/…">` as `<picture>`. Pure: hand it any lookup. |
|
|
2001
2002
|
| `siteImageMarkup(config, html)` | The same, over one site's own records, deriving in the background for a record miss. |
|
|
2002
|
-
| `siteIcons(config,
|
|
2003
|
+
| `siteIcons(config, setting)` | The head's icon links for a site's icon setting: `{ rel, sizes, href }`, or none. |
|
|
2003
2004
|
|
|
2004
2005
|
`describeImage` is synchronous and cached because it is called once per image
|
|
2005
2006
|
while a page renders, and decision-10 forbids probing an image file at render
|
|
@@ -2009,10 +2010,11 @@ markup that asks for them back, and the first request for each one rebuilds it.
|
|
|
2009
2010
|
|
|
2010
2011
|
The site's icons are derived copies of the same kind, and `siteIcons` is how a
|
|
2011
2012
|
head gets at them: three square PNGs, `icon` at 32 and 16 pixels and
|
|
2012
|
-
`apple-touch-icon` at 180, cropped from the middle of the site's
|
|
2013
|
+
`apple-touch-icon` at 180, cropped from the middle of the site's icon (the
|
|
2014
|
+
`icon` that Settings > General writes, else a hand-set `avatar`) and
|
|
2013
2015
|
encoded the first time a browser asks for one. It answers an empty list — and
|
|
2014
|
-
the packaged theme then links no icon at all — when the site has no
|
|
2015
|
-
its
|
|
2016
|
+
the packaged theme then links no icon at all — when the site has no icon, when
|
|
2017
|
+
its icon is a file no icon can be made of, or when `imageOptimization` is off,
|
|
2016
2018
|
so a head never advertises a URL this server would answer 404 for.
|
|
2017
2019
|
|
|
2018
2020
|
`documentContext(document, images)` is where the rewrite is applied. Passing the
|
|
@@ -2223,6 +2225,15 @@ name another host. A theme that signs readers in or takes payment through a
|
|
|
2223
2225
|
popup on another origin needs `same-origin-allow-popups` for COOP. The CMS
|
|
2224
2226
|
itself does neither.
|
|
2225
2227
|
|
|
2228
|
+
One response is framed elsewhere on purpose: the embed view at
|
|
2229
|
+
`/_geekity/embed?url=…`, the card that the oEmbed `html` frames so that
|
|
2230
|
+
WordPress keeps it. It sends a complete policy of its own instead of the
|
|
2231
|
+
default: `frame-ancestors *`, `default-src 'none'`, and its one inline style and
|
|
2232
|
+
one inline script by hash. It never sends `X-Frame-Options`, even one set in
|
|
2233
|
+
`securityHeaders`. It shows nothing from a session, so a signed-in user who is
|
|
2234
|
+
framed sees the same card as anyone else. Every other response keeps the
|
|
2235
|
+
framing headers above.
|
|
2236
|
+
|
|
2226
2237
|
A site changes them with `securityHeaders` in its config. Names are matched
|
|
2227
2238
|
without regard to case. A string replaces a default or adds a header of the
|
|
2228
2239
|
site's own, and `false` removes a default:
|
|
@@ -2363,6 +2374,9 @@ Booting mounts the public site on the app. The routes are:
|
|
|
2363
2374
|
| `/sitemap.xml` | Every public URL, for a search engine. |
|
|
2364
2375
|
| `/sitemap-{n}.xml` | One file of a sitemap too big to be a single one. |
|
|
2365
2376
|
| `/robots.txt` | What a crawler may have, and where the sitemap is. |
|
|
2377
|
+
| `/opensearch.xml` | The OpenSearch description, so a browser can offer the site's search from its address bar. |
|
|
2378
|
+
| `/_geekity/oembed?url=…` | The oEmbed card of the post or page at `url`, as JSON or with `format=xml`; a 404 for anything else. |
|
|
2379
|
+
| `/_geekity/embed?url=…` | The same card as a page any site may frame, which the oEmbed `html` puts in an iframe; a 404 for anything else. |
|
|
2366
2380
|
| `/llms.txt` | The site's pages and recent posts for a language model, each linked to its Markdown. See [below](#llmstxt). |
|
|
2367
2381
|
| `/{key}.txt` | The IndexNow key, while the site has IndexNow on. See [below](#indexnow). |
|
|
2368
2382
|
| `/.well-known/security.txt` | Where to report a vulnerability; a 404 until a security contact is set. See [above](#securitytxt-and-change-password). |
|
|
@@ -2760,7 +2774,7 @@ more than an archive page, so a reader that polls once a day does not miss a
|
|
|
2760
2774
|
post on a site that publishes several.
|
|
2761
2775
|
|
|
2762
2776
|
```json
|
|
2763
|
-
{ "title": "My Site", "tagline": "Notes", "author": "
|
|
2777
|
+
{ "title": "My Site", "tagline": "Notes", "author": "me", "feedSize": 20 }
|
|
2764
2778
|
```
|
|
2765
2779
|
|
|
2766
2780
|
Every RSS feed, the comments feeds included, tells a reader how often to poll
|
|
@@ -2857,8 +2871,22 @@ to read them on, `wfw:commentRss` (the [Well-Formed Web][wfw-ns] comment API) is
|
|
|
2857
2871
|
the feed to poll, and `source:comments` is that same feed with a `count`
|
|
2858
2872
|
attribute, so a reader can say "3 comments" without fetching anything.
|
|
2859
2873
|
|
|
2874
|
+
When the site has a license (Settings > General, or `license` in `site.json`),
|
|
2875
|
+
the channel carries a `creativeCommons:license` holding its URL, and every item
|
|
2876
|
+
carries one for its own license: the post's `license` front matter, else the
|
|
2877
|
+
site's. A post whose front matter says `license: none` carries none. The
|
|
2878
|
+
element is the [Creative Commons RSS module][cc-rss], declared as
|
|
2879
|
+
`xmlns:creativeCommons` only when some license is printed. It names a license
|
|
2880
|
+
by URL, Creative Commons or not, which a reader can act on; `dc:rights` would be
|
|
2881
|
+
free text. Nothing about a license is printed on a site and posts without one.
|
|
2882
|
+
RSS has no element that says "no license", so a post saying `license: none`
|
|
2883
|
+
on a licensed site carries no item license while the channel still names the
|
|
2884
|
+
site's; a reader that applies the channel's license to every item reads that
|
|
2885
|
+
post as under it.
|
|
2886
|
+
|
|
2860
2887
|
[source-ns]: https://source.scripting.com/
|
|
2861
2888
|
[wfw-ns]: http://wellformedweb.org/CommentAPI/
|
|
2889
|
+
[cc-rss]: https://cyber.harvard.edu/rss/creativeCommonsRssModule.html
|
|
2862
2890
|
|
|
2863
2891
|
### Comments
|
|
2864
2892
|
|
|
@@ -2913,6 +2941,14 @@ post as `content type="html"`. The feed itself carries `id`, `title`,
|
|
|
2913
2941
|
`xml:lang` from the `language` setting, and — when the site names a notify
|
|
2914
2942
|
server — a `source:cloud` and a `link rel="hub"`.
|
|
2915
2943
|
|
|
2944
|
+
With a license, the feed carries an [RFC 4946][rfc4946] `link rel="license"`
|
|
2945
|
+
to the site's license, its name as the `title`, and every entry carries one for
|
|
2946
|
+
its own license, the post's `license` front matter else the site's. An entry
|
|
2947
|
+
states its own because RFC 4946 does not let an entry inherit the feed's. JSON
|
|
2948
|
+
Feed has no field for a license and says nothing about one.
|
|
2949
|
+
|
|
2950
|
+
[rfc4946]: https://www.rfc-editor.org/rfc/rfc4946
|
|
2951
|
+
|
|
2916
2952
|
A JSON Feed item carries `id` (the object id), `url` (the permalink), `title`,
|
|
2917
2953
|
`content_html`, `summary`, `date_published`, `date_modified`, `tags` (the
|
|
2918
2954
|
terms) and `authors`; the feed carries `version`, `title`, `home_page_url`,
|
|
@@ -2923,6 +2959,93 @@ The XML is written by this package rather than by a library. Text is escaped;
|
|
|
2923
2959
|
`content:encoded` and `source:markdown` are CDATA sections, with any `]]>` in
|
|
2924
2960
|
the text split across two sections so it cannot end one early.
|
|
2925
2961
|
|
|
2962
|
+
### A recording on a post
|
|
2963
|
+
|
|
2964
|
+
A post can carry one recording: a spoken version of an article, or an episode
|
|
2965
|
+
now and then. The site is a blog that sometimes has audio, not a podcast host,
|
|
2966
|
+
so the feeds carry what a podcast app needs to play, describe and caption an
|
|
2967
|
+
item, and leave the podcast directory tags out. The front matter spells it the
|
|
2968
|
+
way RSS does:
|
|
2969
|
+
|
|
2970
|
+
```yaml
|
|
2971
|
+
enclosure:
|
|
2972
|
+
url: /uploads/2026/10/episode-12.mp3 # an upload; written by the editor
|
|
2973
|
+
type: audio/mpeg # from the upload table; written by the editor
|
|
2974
|
+
length: 23456789 # bytes, from the file on disk; written by the editor
|
|
2975
|
+
duration: 1834 # seconds, optional
|
|
2976
|
+
transcript: # optional
|
|
2977
|
+
url: /uploads/2026/10/episode-12.vtt # an upload or an http(s) URL
|
|
2978
|
+
type: text/vtt # text/vtt, application/x-subrip, text/html or text/plain
|
|
2979
|
+
alternates: # optional, any number
|
|
2980
|
+
- url: /uploads/2026/10/episode-12.mp4
|
|
2981
|
+
type: video/mp4
|
|
2982
|
+
length: 98765432
|
|
2983
|
+
title: Video # optional, at most 32 characters
|
|
2984
|
+
height: 720 # optional, for video
|
|
2985
|
+
lang: en # optional
|
|
2986
|
+
- url: https://cdn.example.com/episode-12-low.mp3
|
|
2987
|
+
type: audio/mpeg # required for a link
|
|
2988
|
+
title: Low bandwidth # a link's length is optional
|
|
2989
|
+
```
|
|
2990
|
+
|
|
2991
|
+
**The main file is always an upload** under `/uploads/`, because RSS requires
|
|
2992
|
+
its `url`, `length` and `type`, and an upload is the only file whose length the
|
|
2993
|
+
site knows. A main file without all three is no recording at all. A bad
|
|
2994
|
+
alternate version or transcript is dropped on its own, so one wrong line in a
|
|
2995
|
+
hand-edited file costs that line and not the episode. `enclosureOf(extra)` is
|
|
2996
|
+
the one reading of the key, and the feeds, the theme and the federation read
|
|
2997
|
+
only what it returns.
|
|
2998
|
+
|
|
2999
|
+
**The post editor** has a Recording section on posts. The main file is a select
|
|
3000
|
+
of the audio and video in the media library, and None removes the whole key,
|
|
3001
|
+
transcript and alternate versions included. The duration takes seconds, `30:34`
|
|
3002
|
+
or `1:02:03`. The transcript is an upload (`.vtt`, `.srt` or `.txt`) or an
|
|
3003
|
+
`https` address with its type. Alternate versions are numbered rows with one
|
|
3004
|
+
blank row at the end, so a plain form can add, edit and remove them; emptying a
|
|
3005
|
+
row's address removes it. On save, the type and length of every upload are read
|
|
3006
|
+
from the upload table and from the file on disk, never from the form. A main
|
|
3007
|
+
file that is not an audio or video upload, a linked version without an `http`
|
|
3008
|
+
or `https` address or a media type, or a title over 32 characters is refused
|
|
3009
|
+
with a message, and nothing is written.
|
|
3010
|
+
|
|
3011
|
+
**What the feeds print, and when.** A post with no recording prints none of
|
|
3012
|
+
this, and a feed with no recording in it declares neither namespace, so its
|
|
3013
|
+
bytes and its ETag are what they were before recordings existed.
|
|
3014
|
+
|
|
3015
|
+
| Element | Format | Printed when |
|
|
3016
|
+
| ----------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3017
|
+
| `<enclosure url length type/>` | RSS item | The post has a main file. `url` is absolute. |
|
|
3018
|
+
| `<podcast:alternateEnclosure>` with `<podcast:source uri/>` | RSS item | Once per alternate version, with `type`, and `length`, `title`, `height` and `lang` when known. |
|
|
3019
|
+
| `<podcast:transcript url type/>` | RSS item | The post has a transcript, with `rel="captions"` for WebVTT or SubRip. |
|
|
3020
|
+
| `<itunes:duration>` | RSS item | The duration is known, in whole seconds. |
|
|
3021
|
+
| `<podcast:medium>blog</podcast:medium>` | RSS channel | Any item in the feed has a recording. Without it an app assumes `podcast`; `blog` is the spec's word for articles that sometimes have audio. |
|
|
3022
|
+
| `<link rel="enclosure" type length href/>` | Atom entry | The post has a main file. Atom has no way to say the others are versions of it. |
|
|
3023
|
+
| `attachments` | JSON Feed | The main file and then each alternate version, with `url`, `mime_type`, `size_in_bytes` and `title` when known, and `duration_in_seconds` on audio or video when the duration is known. |
|
|
3024
|
+
|
|
3025
|
+
`xmlns:podcast="https://podcastindex.org/namespace/1.0"` is declared only on a
|
|
3026
|
+
feed that prints a podcast element, and
|
|
3027
|
+
`xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"` only on one that
|
|
3028
|
+
prints `itunes:duration`.
|
|
3029
|
+
|
|
3030
|
+
**Left out on purpose:** the Apple directory tags (`itunes:image`,
|
|
3031
|
+
`itunes:category`, `itunes:explicit`, `itunes:owner`), and `podcast:guid`,
|
|
3032
|
+
`podcast:locked`, `podcast:person`, `podcast:season`, `podcast:episode`,
|
|
3033
|
+
`podcast:value` and `podcast:chapters`. Each one matters only to a podcast
|
|
3034
|
+
directory or a dedicated show. Adding them is a follow-up for a site that wants
|
|
3035
|
+
to be listed in Apple Podcasts, and so is `podcast:license`, which could reuse
|
|
3036
|
+
the [content license](#rss-20).
|
|
3037
|
+
|
|
3038
|
+
**The fediverse** gets the main file as an `Audio` or `Video` attachment with
|
|
3039
|
+
its absolute `url`, its `mediaType`, and the post's title, or a note's first
|
|
3040
|
+
words, as its `name`. It goes before the post's `Image` attachments, because
|
|
3041
|
+
Mastodon shows a player or a gallery by the type of the first attachment and
|
|
3042
|
+
keeps only four. Alternate versions and linked files stay out: a remote server
|
|
3043
|
+
fetches and re-encodes what it is given, and a link to another host is a type
|
|
3044
|
+
this site cannot check. `playsAsVideo(type)` decides `Video` against `Audio`,
|
|
3045
|
+
and the default theme's `<video>` against `<audio>`, so the two never disagree.
|
|
3046
|
+
|
|
3047
|
+
The default theme plays the main file on the post page; see its README.
|
|
3048
|
+
|
|
2926
3049
|
### Real-time notification
|
|
2927
3050
|
|
|
2928
3051
|
A feed reader that polls hears about a post when it next polls. An [rssCloud][]
|
|
@@ -3365,8 +3488,9 @@ Tailwind. Every colour is a `--color-*` custom property a site can override.
|
|
|
3365
3488
|
`base.njk` is its shell: a skip link, a `.global-wrapper` carrying `data-is-root-path="true"`
|
|
3366
3489
|
at `/` only, a header that is the site title and tagline on the front page and a
|
|
3367
3490
|
small link home on every other — with `menus.primary` inside it on every page
|
|
3368
|
-
— `<main id="main">`, and a footer with the copyright year, the site author
|
|
3369
|
-
the
|
|
3491
|
+
— `<main id="main">`, and a footer with the copyright year, the site author's
|
|
3492
|
+
display name (the site title on a site with several authors), the colophon and
|
|
3493
|
+
`menus.footer`. The footer reads nothing off an account: it
|
|
3370
3494
|
used to print an RSS link and one `rel="me"` link per link on the site author's
|
|
3371
3495
|
profile, and a site that wants either types it into its footer menu. Nothing
|
|
3372
3496
|
particular to one site is in it — webrings and badges belong in a site theme's
|
|
@@ -62,6 +62,75 @@
|
|
|
62
62
|
formnovalidate
|
|
63
63
|
>Preview</button>
|
|
64
64
|
</div>
|
|
65
|
+
|
|
66
|
+
{% if kind.type == "post" %}
|
|
67
|
+
<fieldset class="admin-editor-enclosure">
|
|
68
|
+
<legend>Recording</legend>
|
|
69
|
+
|
|
70
|
+
<label for="editor-enclosure-url">Audio or video file</label>
|
|
71
|
+
<select id="editor-enclosure-url" name="{{ enclosureFields.url }}" aria-describedby="editor-enclosure-url-hint">
|
|
72
|
+
<option value=""{% if form.enclosure.url == "" %} selected{% endif %}>None</option>
|
|
73
|
+
{% for choice in enclosureChoices.media %}
|
|
74
|
+
<option value="{{ choice.url }}"{% if choice.url == form.enclosure.url %} selected{% endif %}>{{ choice.label }}</option>
|
|
75
|
+
{% endfor %}
|
|
76
|
+
</select>
|
|
77
|
+
<p class="admin-hint" id="editor-enclosure-url-hint">
|
|
78
|
+
Upload the file to the media library first. Feeds carry it as the post’s enclosure and the
|
|
79
|
+
post page plays it. None removes the recording, its transcript and its other versions.
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
<label for="editor-enclosure-duration">Duration</label>
|
|
83
|
+
<input id="editor-enclosure-duration" name="{{ enclosureFields.duration }}" type="text" inputmode="numeric" value="{{ form.enclosure.duration }}" autocomplete="off" aria-describedby="editor-enclosure-duration-hint" />
|
|
84
|
+
<p class="admin-hint" id="editor-enclosure-duration-hint">Seconds, or 30:34, or 1:02:03. Leave it empty if you do not know.</p>
|
|
85
|
+
|
|
86
|
+
<label for="editor-enclosure-transcript">Transcript or captions</label>
|
|
87
|
+
<input id="editor-enclosure-transcript" name="{{ enclosureFields.transcriptUrl }}" type="text" value="{{ form.enclosure.transcriptUrl }}" list="editor-transcript-uploads" autocomplete="off" aria-describedby="editor-enclosure-transcript-hint" />
|
|
88
|
+
<datalist id="editor-transcript-uploads">
|
|
89
|
+
{% for url in enclosureChoices.transcripts %}
|
|
90
|
+
<option value="{{ url }}"></option>
|
|
91
|
+
{% endfor %}
|
|
92
|
+
</datalist>
|
|
93
|
+
<label for="editor-enclosure-transcript-type">Transcript type</label>
|
|
94
|
+
<select id="editor-enclosure-transcript-type" name="{{ enclosureFields.transcriptType }}" aria-describedby="editor-enclosure-transcript-hint">
|
|
95
|
+
<option value=""{% if form.enclosure.transcriptType == "" %} selected{% endif %}>From the file</option>
|
|
96
|
+
{% for type in transcriptTypes %}
|
|
97
|
+
<option value="{{ type }}"{% if type == form.enclosure.transcriptType %} selected{% endif %}>{{ type }}</option>
|
|
98
|
+
{% endfor %}
|
|
99
|
+
</select>
|
|
100
|
+
<p class="admin-hint" id="editor-enclosure-transcript-hint">
|
|
101
|
+
A .vtt, .srt or .txt file in the media library, or an address starting https://. A linked
|
|
102
|
+
transcript needs its type.
|
|
103
|
+
</p>
|
|
104
|
+
|
|
105
|
+
<h2 class="admin-editor-enclosure-heading">Other versions</h2>
|
|
106
|
+
<p class="admin-hint">
|
|
107
|
+
A video of an audio episode, a smaller file, another language. Each is a file in the media
|
|
108
|
+
library or an address starting https://, which needs its type. Empty an address to remove
|
|
109
|
+
that version.
|
|
110
|
+
</p>
|
|
111
|
+
{% for row in alternateRows %}
|
|
112
|
+
{% set n = loop.index0 %}
|
|
113
|
+
<fieldset class="admin-editor-alternate">
|
|
114
|
+
<legend>{% if row.url %}Version {{ loop.index }}{% else %}Add a version{% endif %}</legend>
|
|
115
|
+
<label for="editor-alternate-url-{{ n }}">Address</label>
|
|
116
|
+
<input id="editor-alternate-url-{{ n }}" name="{{ enclosureFields.alternate.url }}{{ n }}" type="text" value="{{ row.url }}" list="editor-media-uploads" autocomplete="off" />
|
|
117
|
+
<label for="editor-alternate-type-{{ n }}">Type</label>
|
|
118
|
+
<input id="editor-alternate-type-{{ n }}" name="{{ enclosureFields.alternate.type }}{{ n }}" type="text" value="{{ row.type }}" placeholder="video/mp4" autocomplete="off" spellcheck="false" />
|
|
119
|
+
<label for="editor-alternate-title-{{ n }}">Title</label>
|
|
120
|
+
<input id="editor-alternate-title-{{ n }}" name="{{ enclosureFields.alternate.title }}{{ n }}" type="text" value="{{ row.title }}" maxlength="32" />
|
|
121
|
+
<label for="editor-alternate-height-{{ n }}">Height</label>
|
|
122
|
+
<input id="editor-alternate-height-{{ n }}" name="{{ enclosureFields.alternate.height }}{{ n }}" type="text" inputmode="numeric" value="{{ row.height }}" autocomplete="off" />
|
|
123
|
+
<label for="editor-alternate-lang-{{ n }}">Language</label>
|
|
124
|
+
<input id="editor-alternate-lang-{{ n }}" name="{{ enclosureFields.alternate.lang }}{{ n }}" type="text" value="{{ row.lang }}" autocomplete="off" spellcheck="false" />
|
|
125
|
+
</fieldset>
|
|
126
|
+
{% endfor %}
|
|
127
|
+
<datalist id="editor-media-uploads">
|
|
128
|
+
{% for choice in enclosureChoices.media %}
|
|
129
|
+
<option value="{{ choice.url }}"></option>
|
|
130
|
+
{% endfor %}
|
|
131
|
+
</datalist>
|
|
132
|
+
</fieldset>
|
|
133
|
+
{% endif %}
|
|
65
134
|
</div>
|
|
66
135
|
|
|
67
136
|
<div class="admin-editor-side">
|
|
@@ -128,6 +197,19 @@
|
|
|
128
197
|
<label for="editor-draft">Draft</label>
|
|
129
198
|
</p>
|
|
130
199
|
|
|
200
|
+
{% if kind.type == "post" %}
|
|
201
|
+
{# TASK-207: shown at the top of the author's fediverse profile, from
|
|
202
|
+
the featured collection their actor links. #}
|
|
203
|
+
<p class="admin-check">
|
|
204
|
+
<input id="editor-pinned" name="pinned" type="checkbox" value="1"{% if form.pinned %} checked{% endif %} />
|
|
205
|
+
<label for="editor-pinned">Pinned</label>
|
|
206
|
+
</p>
|
|
207
|
+
<p class="admin-hint">
|
|
208
|
+
Writes <code>pinned:</code> and the moment you pinned it into the front matter. Pinned posts
|
|
209
|
+
show first on your fediverse profile, newest pin first, up to five.
|
|
210
|
+
</p>
|
|
211
|
+
{% endif %}
|
|
212
|
+
|
|
131
213
|
{# What this document says about comments, if anything. Three answers
|
|
132
214
|
rather than two: the site's own rules, always open, always closed. #}
|
|
133
215
|
<label for="editor-comments">Comments</label>
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
<label for="media-file">Add a file</label>
|
|
49
49
|
<input id="media-file" name="{{ fields.file }}" type="file" accept="{{ accepts }}" required />
|
|
50
50
|
<p class="admin-hint">
|
|
51
|
-
This site accepts {{ accepts | replace(',', ', ') }}, up to {{ maxBytes }} bytes
|
|
51
|
+
This site accepts {{ accepts | replace(',', ', ') }}, up to {{ maxBytes }} bytes, or
|
|
52
|
+
{{ mediaMaxBytes }} bytes for audio and video. The same
|
|
52
53
|
rules the editor’s upload control enforces.
|
|
53
54
|
</p>
|
|
54
55
|
<p class="admin-settings-buttons">
|
|
@@ -76,7 +77,7 @@
|
|
|
76
77
|
{% for file in files %}
|
|
77
78
|
<tr>
|
|
78
79
|
<td class="admin-media-file">
|
|
79
|
-
{% if file.image %}
|
|
80
|
+
{% if file.kind == 'image' %}
|
|
80
81
|
<a href="{{ file.url }}">
|
|
81
82
|
<img class="admin-media-thumbnail" src="{{ file.url }}" alt="" width="60" height="60" />
|
|
82
83
|
</a>
|
|
@@ -86,7 +87,7 @@
|
|
|
86
87
|
<a href="{{ file.url }}">{{ file.path }}</a>
|
|
87
88
|
</td>
|
|
88
89
|
<td class="admin-media-alt">
|
|
89
|
-
{% if file.image %}
|
|
90
|
+
{% if file.kind == 'image' %}
|
|
90
91
|
<form method="post" action="{{ altUrl }}">
|
|
91
92
|
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
92
93
|
<input type="hidden" name="{{ fields.path }}" value="{{ file.path }}" />
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
{{ field.problem('settings-timezone', problems.timezone) }}
|
|
9
9
|
{{ field.problem('settings-language', problems.language) }}
|
|
10
10
|
{{ field.problem('settings-locale', problems.locale) }}
|
|
11
|
+
{{ field.problem('settings-icon', problems.icon) }}
|
|
12
|
+
{{ field.problem('settings-license', problems.license) }}
|
|
13
|
+
{{ field.problem('settings-license-url', problems.licenseUrl) }}
|
|
14
|
+
{{ field.problem('settings-license-name', problems.licenseName) }}
|
|
11
15
|
{% endblock %}
|
|
12
16
|
|
|
13
17
|
{% block settingsFields %}
|
|
@@ -17,12 +21,13 @@
|
|
|
17
21
|
{{ field.text('settings-tagline', fields.tagline, 'Tagline', form.tagline,
|
|
18
22
|
hint='One line, shown under the title and used as the feed subtitle.') }}
|
|
19
23
|
|
|
20
|
-
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
{% set authorHint %}A user makes this their blog: the homepage shows their bio and links, claims their author archive and profiles with <code>rel="me"</code>, and is their IndieAuth sign-in URL. The feeds and the footer credit them by name. Several authors credits the site title instead.{% endset %}
|
|
25
|
+
{% call field.select('settings-author', fields.author, 'Site author', hint=authorHint) %}
|
|
26
|
+
<option value=""{% if not form.author %} selected{% endif %}>Several authors</option>
|
|
27
|
+
{% for choice in authorChoices %}
|
|
28
|
+
<option value="{{ choice.username }}"{% if choice.username == form.author %} selected{% endif %}>{{ choice.name }}</option>
|
|
29
|
+
{% endfor %}
|
|
30
|
+
{% endcall %}
|
|
26
31
|
|
|
27
32
|
{% set baseUrlHint %}In effect: <code>{{ baseUrlInEffect }}</code>. {{ baseUrlNote }}{% endset %}
|
|
28
33
|
{{ field.text('settings-base-url', fields.baseUrl, 'Base URL', form.baseUrl,
|
|
@@ -40,6 +45,46 @@
|
|
|
40
45
|
{% set localeHint %}How the public site writes dates and counts, such as <code>en-US</code> for <code>September 2, 2026</code>. Leave it empty to follow the language.{% endset %}
|
|
41
46
|
{{ field.text('settings-locale', fields.locale, 'Locale', form.locale,
|
|
42
47
|
error=problems.locale, hint=localeHint) }}
|
|
48
|
+
|
|
49
|
+
{% set iconHint %}A square image at least 512 pixels wide, uploaded on <a href="{{ adminUrl }}/media">Media</a>: a path such as <code>/uploads/2026/10/icon.png</code>. It is the favicon, the home screen icon, <code>/favicon.ico</code> and the web app manifest's icon. Anything not square is cropped to its middle.{% endset %}
|
|
50
|
+
{{ field.text('settings-icon', fields.icon, 'Site icon', form.icon,
|
|
51
|
+
error=problems.icon, hint=iconHint) }}
|
|
52
|
+
|
|
53
|
+
<div class="admin-avatar-current">
|
|
54
|
+
{% if iconPreview %}
|
|
55
|
+
<img class="admin-icon-preview" src="{{ iconPreview }}" alt="The current site icon" width="96" height="96" />
|
|
56
|
+
{% else %}
|
|
57
|
+
<span class="admin-icon-preview admin-avatar-blank"></span>
|
|
58
|
+
{% endif %}
|
|
59
|
+
<p class="admin-hint">
|
|
60
|
+
{% if iconUnderived and not imageOptimization %}
|
|
61
|
+
Image optimization is off, so no icons are derived and the site links none.
|
|
62
|
+
{% elif iconUnderived %}
|
|
63
|
+
The site cannot derive icons from <code>{{ iconInEffect }}</code>, so it links none.
|
|
64
|
+
{% elif iconPreview and iconFromAvatar %}
|
|
65
|
+
The current icon, from the <code>avatar</code> in <code>site.json</code>. Saving this page writes it as the icon; clearing the field leaves the avatar in use.
|
|
66
|
+
{% elif iconPreview %}
|
|
67
|
+
The current icon, as a browser tab and a home screen show it.
|
|
68
|
+
{% else %}
|
|
69
|
+
The site has no icon yet, so browsers show their own.
|
|
70
|
+
{% endif %}
|
|
71
|
+
</p>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{% set licenseHint %}What readers may do with your posts. The footer links it, and the structured data and the feeds declare it. A post can name its own with <code>license</code> in its front matter.{% endset %}
|
|
75
|
+
{% call field.select('settings-license', fields.license, 'License', error=problems.license, hint=licenseHint) %}
|
|
76
|
+
<option value=""{% if not form.license %} selected{% endif %}>No license (all rights reserved)</option>
|
|
77
|
+
{% for choice in licenseChoices %}
|
|
78
|
+
<option value="{{ choice.key }}"{% if choice.key == form.license %} selected{% endif %}>{{ choice.name }}</option>
|
|
79
|
+
{% endfor %}
|
|
80
|
+
<option value="custom"{% if form.license == 'custom' %} selected{% endif %}>Custom license</option>
|
|
81
|
+
{% endcall %}
|
|
82
|
+
|
|
83
|
+
{{ field.text('settings-license-url', fields.licenseUrl, 'Custom license URL', form.licenseUrl,
|
|
84
|
+
error=problems.licenseUrl, inputmode='url', hint='Where its terms are. Only read for a custom license.') }}
|
|
85
|
+
|
|
86
|
+
{{ field.text('settings-license-name', fields.licenseName, 'Custom license name', form.licenseName,
|
|
87
|
+
error=problems.licenseName, hint='What the footer calls it. Only read for a custom license.') }}
|
|
43
88
|
{% endblock %}
|
|
44
89
|
|
|
45
90
|
{% block settingsPanels %}{% endblock %}
|
package/admin/static/admin.css
CHANGED
|
@@ -490,6 +490,31 @@ button[type='submit']:hover,
|
|
|
490
490
|
min-height: 28rem;
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
+
.admin-editor-enclosure {
|
|
494
|
+
margin: 1.25rem 0 0;
|
|
495
|
+
padding: 0.75rem 1rem 1rem;
|
|
496
|
+
border: 1px solid var(--admin-border);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.admin-editor-enclosure-heading {
|
|
500
|
+
margin: 1.25rem 0 0;
|
|
501
|
+
font-size: 1em;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.admin-editor-alternate {
|
|
505
|
+
display: grid;
|
|
506
|
+
grid-template-columns: max-content minmax(0, 1fr);
|
|
507
|
+
gap: 0.4rem 0.75rem;
|
|
508
|
+
align-items: center;
|
|
509
|
+
margin: 0.75rem 0 0;
|
|
510
|
+
padding: 0.5rem 0.75rem 0.75rem;
|
|
511
|
+
border: 1px solid var(--admin-border);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.admin-editor-alternate label {
|
|
515
|
+
margin: 0;
|
|
516
|
+
}
|
|
517
|
+
|
|
493
518
|
.admin-hint {
|
|
494
519
|
margin: 0.25rem 0 0;
|
|
495
520
|
color: var(--admin-muted);
|
|
@@ -792,6 +817,14 @@ button[type='submit']:hover,
|
|
|
792
817
|
border: 1px solid var(--admin-border);
|
|
793
818
|
}
|
|
794
819
|
|
|
820
|
+
.admin-icon-preview {
|
|
821
|
+
flex: none;
|
|
822
|
+
width: 6rem;
|
|
823
|
+
height: 6rem;
|
|
824
|
+
border: 1px solid var(--admin-border);
|
|
825
|
+
border-radius: 0.75rem;
|
|
826
|
+
}
|
|
827
|
+
|
|
795
828
|
.admin-avatar-current {
|
|
796
829
|
display: flex;
|
|
797
830
|
gap: 1rem;
|
|
@@ -2,6 +2,7 @@ import type { Context, Hono } from 'hono';
|
|
|
2
2
|
import type { Document, DocumentType } from '../content/document.ts';
|
|
3
3
|
import type { ContentStore, ListAllOptions } from '../content/store.ts';
|
|
4
4
|
import type { GeekityEnv } from '../env.ts';
|
|
5
|
+
import type { EnclosureForm } from './enclosure-field.ts';
|
|
5
6
|
/**
|
|
6
7
|
* Everything that differs between the posts screens and the pages screens.
|
|
7
8
|
*
|
|
@@ -168,6 +169,8 @@ export interface EditorForm {
|
|
|
168
169
|
exclude: boolean;
|
|
169
170
|
/** Whether the page offers a contact form. Pages only. */
|
|
170
171
|
contact: boolean;
|
|
172
|
+
/** Whether the post is pinned to its author's profile (TASK-207). Posts only. */
|
|
173
|
+
pinned: boolean;
|
|
171
174
|
/**
|
|
172
175
|
* What the document says about comments: one of {@link COMMENT_SETTINGS}.
|
|
173
176
|
*
|
|
@@ -177,6 +180,8 @@ export interface EditorForm {
|
|
|
177
180
|
* lose is the default every document starts at.
|
|
178
181
|
*/
|
|
179
182
|
comments: string;
|
|
183
|
+
/** The post's recording (TASK-213). Posts only; blank on a page. */
|
|
184
|
+
enclosure: EnclosureForm;
|
|
180
185
|
body: string;
|
|
181
186
|
/** The hash of the file the form was filled in from; empty for a new one. */
|
|
182
187
|
hash: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/admin/documents.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,QAAQ,EAAmB,YAAY,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/admin/documents.ts"],"names":[],"mappings":"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"}
|