@geekity/cms 0.14.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 +163 -52
- package/admin/pages/documents/editor.njk +69 -0
- package/admin/pages/media/library.njk +4 -3
- package/admin/pages/settings/general.njk +44 -0
- package/admin/static/admin.css +33 -0
- package/dist/admin/documents.d.ts +3 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +38 -5
- 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/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 +36 -2
- 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 +26 -0
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +88 -2
- 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 +17 -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/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +35 -6
- package/dist/federation/article.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/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.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/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 +20 -0
- package/dist/web/feed-atom.js.map +1 -1
- package/dist/web/feed-item.d.ts +19 -0
- package/dist/web/feed-item.d.ts.map +1 -1
- package/dist/web/feed-item.js +22 -0
- package/dist/web/feed-item.js.map +1 -1
- package/dist/web/feed-json.d.ts +13 -0
- package/dist/web/feed-json.d.ts.map +1 -1
- package/dist/web/feed-json.js +15 -0
- 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 +59 -2
- package/dist/web/feed-rss.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 +6 -0
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/index.d.ts +4 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +2 -0
- 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.map +1 -1
- package/dist/web/oembed.js +1 -2
- package/dist/web/oembed.js.map +1 -1
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +11 -2
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +8 -4
- 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/package.json +1 -1
- package/themes/default/README.md +81 -28
- package/themes/default/layouts/base.njk +3 -4
- package/themes/default/layouts/post.njk +4 -0
- package/themes/default/partials/jsonld.njk +4 -6
- package/themes/default/partials/recording.njk +41 -0
- package/themes/default/src/style.css +16 -0
- package/themes/default/static/style.css +25 -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
|
|
@@ -1846,7 +1846,8 @@ at once cannot each keep half of what the other kept.
|
|
|
1846
1846
|
|
|
1847
1847
|
The file carries `title`, `tagline`, `url`, `author` (a username, when the
|
|
1848
1848
|
site has one author), `postsPerPage`, `homepage`, `postsPage`,
|
|
1849
|
-
`timezone`, `language`, `locale` (when it is set), `
|
|
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
|
|
@@ -2869,8 +2871,22 @@ to read them on, `wfw:commentRss` (the [Well-Formed Web][wfw-ns] comment API) is
|
|
|
2869
2871
|
the feed to poll, and `source:comments` is that same feed with a `count`
|
|
2870
2872
|
attribute, so a reader can say "3 comments" without fetching anything.
|
|
2871
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
|
+
|
|
2872
2887
|
[source-ns]: https://source.scripting.com/
|
|
2873
2888
|
[wfw-ns]: http://wellformedweb.org/CommentAPI/
|
|
2889
|
+
[cc-rss]: https://cyber.harvard.edu/rss/creativeCommonsRssModule.html
|
|
2874
2890
|
|
|
2875
2891
|
### Comments
|
|
2876
2892
|
|
|
@@ -2925,6 +2941,14 @@ post as `content type="html"`. The feed itself carries `id`, `title`,
|
|
|
2925
2941
|
`xml:lang` from the `language` setting, and — when the site names a notify
|
|
2926
2942
|
server — a `source:cloud` and a `link rel="hub"`.
|
|
2927
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
|
+
|
|
2928
2952
|
A JSON Feed item carries `id` (the object id), `url` (the permalink), `title`,
|
|
2929
2953
|
`content_html`, `summary`, `date_published`, `date_modified`, `tags` (the
|
|
2930
2954
|
terms) and `authors`; the feed carries `version`, `title`, `home_page_url`,
|
|
@@ -2935,6 +2959,93 @@ The XML is written by this package rather than by a library. Text is escaped;
|
|
|
2935
2959
|
`content:encoded` and `source:markdown` are CDATA sections, with any `]]>` in
|
|
2936
2960
|
the text split across two sections so it cannot end one early.
|
|
2937
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
|
+
|
|
2938
3049
|
### Real-time notification
|
|
2939
3050
|
|
|
2940
3051
|
A feed reader that polls hears about a post when it next polls. An [rssCloud][]
|
|
@@ -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">
|
|
@@ -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 %}
|
|
@@ -41,6 +45,46 @@
|
|
|
41
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 %}
|
|
42
46
|
{{ field.text('settings-locale', fields.locale, 'Locale', form.locale,
|
|
43
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.') }}
|
|
44
88
|
{% endblock %}
|
|
45
89
|
|
|
46
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
|
*
|
|
@@ -179,6 +180,8 @@ export interface EditorForm {
|
|
|
179
180
|
* lose is the default every document starts at.
|
|
180
181
|
*/
|
|
181
182
|
comments: string;
|
|
183
|
+
/** The post's recording (TASK-213). Posts only; blank on a page. */
|
|
184
|
+
enclosure: EnclosureForm;
|
|
182
185
|
body: string;
|
|
183
186
|
/** The hash of the file the form was filled in from; empty for a new one. */
|
|
184
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":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,QAAQ,EAAmB,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAYtF,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AASxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AA4B5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,IAAI,EAAE,YAAY,CAAC;IACnB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,KAAK,EAAE,OAAO,CAAC;IACf,oDAAoD;IACpD,MAAM,EAAE,OAAO,CAAC;IAChB,iEAAiE;IACjE,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;;OAQG;IACH,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,mCAAmC,CAAC;AAE5D;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;aAAK,IAAI,EAAE,EAAE;aAAE,IAAI,EAAE,MAAM;aAAE,MAAM,EAAE,QAAQ;CAAW,CAAC;AAiBtF,2EAA2E;AAC3E,eAAO,MAAM,SAAS,EAAE,YAWvB,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,SAAS,EAAE,YAWvB,CAAC;AAEF,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAExD;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnF,gDAAgD;AAChD,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAMrD,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,CAExE;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,GAAG,cAAc,CAQzF;AAED,mEAAmE;AACnE,MAAM,MAAM,WAAW,GAAG,CACxB,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,EACtB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC9B,QAAQ,CAAC;AAEd,wEAAwE;AACxE,MAAM,WAAW,2BAA2B;IAC1C,2CAA2C;IAC3C,IAAI,EAAE,YAAY,CAAC;IACnB,oFAAoF;IACpF,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,EACrB,OAAO,EAAE,2BAA2B,GACnC,IAAI,CAuEN;AAwgBD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAOjD;AAkCD,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CAkBrF;AA+LD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK5D;AAwBD,iEAAiE;AACjE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,mEAAmE;IACnE,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;;;OAOG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,SAAS,EAAE,aAAa,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,YAAY,EAClB,QAAQ,GAAE,MAAyB,EACnC,GAAG,GAAE,IAAiB,GACrB,UAAU,CAwBZ;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAE,MAAyB,GAAG,UAAU,CAwB3F;AA8FD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,GACX,QAAQ,GAAG,SAAS,CAWtB;AAED,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,SAAS,EAAE,OAAO,CAAC;IACnB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B;;;OAGG;IACH,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;aAAK,QAAQ,EAAE,YAAY;aAAE,SAAS,EAAE,YAAY;CAAW,CAAC;AAoD7F,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,SAAI,GAAG,MAAM,CAMvF"}
|