@geekity/cms 0.24.0 → 0.26.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 +96 -160
- package/admin/pages/documents/editor.njk +48 -0
- package/admin/pages/federation/settings.njk +1 -59
- package/admin/pages/plugins/installed.njk +113 -0
- package/admin/pages/plugins/screen.njk +129 -0
- package/admin/static/admin.css +144 -4
- package/admin/static/editor-actions.js +202 -0
- package/dist/admin/accounts.d.ts +18 -39
- package/dist/admin/accounts.d.ts.map +1 -1
- package/dist/admin/accounts.js +19 -48
- package/dist/admin/accounts.js.map +1 -1
- package/dist/admin/documents.d.ts +5 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +17 -8
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/editor-actions.d.ts +35 -0
- package/dist/admin/editor-actions.d.ts.map +1 -0
- package/dist/admin/editor-actions.js +187 -0
- package/dist/admin/editor-actions.js.map +1 -0
- package/dist/admin/federation.d.ts +4 -5
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +4 -5
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/menu.d.ts +1 -1
- package/dist/admin/menu.d.ts.map +1 -1
- package/dist/admin/menu.js +10 -3
- package/dist/admin/menu.js.map +1 -1
- package/dist/admin/plugins.d.ts +24 -0
- package/dist/admin/plugins.d.ts.map +1 -0
- package/dist/admin/plugins.js +312 -0
- package/dist/admin/plugins.js.map +1 -0
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +5 -1
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/settings-federation.d.ts +0 -25
- package/dist/admin/settings-federation.d.ts.map +1 -1
- package/dist/admin/settings-federation.js +1 -54
- package/dist/admin/settings-federation.js.map +1 -1
- package/dist/admin/settings.d.ts +2 -14
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +1 -21
- package/dist/admin/settings.js.map +1 -1
- package/dist/admin/store.d.ts +8 -1
- package/dist/admin/store.d.ts.map +1 -1
- package/dist/admin/store.js +7 -3
- package/dist/admin/store.js.map +1 -1
- package/dist/admin/templates.d.ts +2 -0
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +2 -0
- package/dist/admin/templates.js.map +1 -1
- package/dist/cli.d.ts +2 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +278 -212
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/content/store.d.ts +6 -0
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +3 -0
- package/dist/content/store.js.map +1 -1
- package/dist/drain.d.ts +28 -0
- package/dist/drain.d.ts.map +1 -0
- package/dist/drain.js +106 -0
- package/dist/drain.js.map +1 -0
- package/dist/env.d.ts +11 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/federation/federation.d.ts +4 -2
- package/dist/federation/federation.d.ts.map +1 -1
- package/dist/federation/federation.js +16 -21
- package/dist/federation/federation.js.map +1 -1
- package/dist/federation/inbox.d.ts +23 -3
- package/dist/federation/inbox.d.ts.map +1 -1
- package/dist/federation/inbox.js +63 -1
- package/dist/federation/inbox.js.map +1 -1
- package/dist/federation/index.d.ts +0 -4
- package/dist/federation/index.d.ts.map +1 -1
- package/dist/federation/index.js +0 -2
- package/dist/federation/index.js.map +1 -1
- package/dist/federation/mount.d.ts +6 -12
- package/dist/federation/mount.d.ts.map +1 -1
- package/dist/federation/mount.js +3 -50
- package/dist/federation/mount.js.map +1 -1
- package/dist/federation/queue.d.ts +22 -0
- package/dist/federation/queue.d.ts.map +1 -0
- package/dist/federation/queue.js +58 -0
- package/dist/federation/queue.js.map +1 -0
- package/dist/files/atomic.d.ts +13 -0
- package/dist/files/atomic.d.ts.map +1 -1
- package/dist/files/atomic.js +45 -0
- package/dist/files/atomic.js.map +1 -1
- package/dist/index.d.ts +30 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +156 -79
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +524 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +23 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugins/enabled.d.ts +23 -0
- package/dist/plugins/enabled.d.ts.map +1 -0
- package/dist/plugins/enabled.js +49 -0
- package/dist/plugins/enabled.js.map +1 -0
- package/dist/plugins/federation.d.ts +26 -0
- package/dist/plugins/federation.d.ts.map +1 -0
- package/dist/plugins/federation.js +114 -0
- package/dist/plugins/federation.js.map +1 -0
- package/dist/plugins/fetch.d.ts +15 -0
- package/dist/plugins/fetch.d.ts.map +1 -0
- package/dist/plugins/fetch.js +33 -0
- package/dist/plugins/fetch.js.map +1 -0
- package/dist/plugins/folder.d.ts +50 -0
- package/dist/plugins/folder.d.ts.map +1 -0
- package/dist/plugins/folder.js +171 -0
- package/dist/plugins/folder.js.map +1 -0
- package/dist/plugins/install.d.ts +45 -0
- package/dist/plugins/install.d.ts.map +1 -0
- package/dist/plugins/install.js +275 -0
- package/dist/plugins/install.js.map +1 -0
- package/dist/plugins/mount.d.ts +17 -0
- package/dist/plugins/mount.d.ts.map +1 -0
- package/dist/plugins/mount.js +44 -0
- package/dist/plugins/mount.js.map +1 -0
- package/dist/plugins/registry.d.ts +110 -0
- package/dist/plugins/registry.d.ts.map +1 -0
- package/dist/plugins/registry.js +447 -0
- package/dist/plugins/registry.js.map +1 -0
- package/dist/plugins/settings.d.ts +64 -0
- package/dist/plugins/settings.d.ts.map +1 -0
- package/dist/plugins/settings.js +219 -0
- package/dist/plugins/settings.js.map +1 -0
- package/dist/plugins/site.d.ts +17 -0
- package/dist/plugins/site.d.ts.map +1 -0
- package/dist/plugins/site.js +67 -0
- package/dist/plugins/site.js.map +1 -0
- package/dist/supervisor/primary.d.ts +47 -0
- package/dist/supervisor/primary.d.ts.map +1 -0
- package/dist/supervisor/primary.js +208 -0
- package/dist/supervisor/primary.js.map +1 -0
- package/dist/supervisor/protocol.d.ts +37 -0
- package/dist/supervisor/protocol.d.ts.map +1 -0
- package/dist/supervisor/protocol.js +29 -0
- package/dist/supervisor/protocol.js.map +1 -0
- package/dist/supervisor/supervision.d.ts +25 -0
- package/dist/supervisor/supervision.d.ts.map +1 -0
- package/dist/supervisor/supervision.js +7 -0
- package/dist/supervisor/supervision.js.map +1 -0
- package/dist/supervisor/worker.d.ts +29 -0
- package/dist/supervisor/worker.d.ts.map +1 -0
- package/dist/supervisor/worker.js +97 -0
- package/dist/supervisor/worker.js.map +1 -0
- package/dist/web/maintenance.d.ts +2 -2
- package/dist/web/maintenance.js +2 -2
- package/dist/web/negotiate.d.ts +4 -3
- package/dist/web/negotiate.d.ts.map +1 -1
- package/dist/web/negotiate.js +4 -3
- package/dist/web/negotiate.js.map +1 -1
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +33 -14
- package/dist/web/routes.js.map +1 -1
- package/package.json +10 -1
- package/dist/comments/conversation.d.ts +0 -29
- package/dist/comments/conversation.d.ts.map +0 -1
- package/dist/comments/conversation.js +0 -60
- package/dist/comments/conversation.js.map +0 -1
- package/dist/federation/import-wordpress.d.ts +0 -128
- package/dist/federation/import-wordpress.d.ts.map +0 -1
- package/dist/federation/import-wordpress.js +0 -426
- package/dist/federation/import-wordpress.js.map +0 -1
- package/dist/federation/wordpress.d.ts +0 -131
- package/dist/federation/wordpress.d.ts.map +0 -1
- package/dist/federation/wordpress.js +0 -342
- package/dist/federation/wordpress.js.map +0 -1
- package/dist/web/comments.d.ts +0 -60
- package/dist/web/comments.d.ts.map +0 -1
- package/dist/web/comments.js +0 -207
- package/dist/web/comments.js.map +0 -1
package/README.md
CHANGED
|
@@ -86,21 +86,26 @@ message on a site whose `site.json` has not set a period.
|
|
|
86
86
|
site (or `npx geekity`, or a `package.json` script, which is how the generated
|
|
87
87
|
`sync` script calls it).
|
|
88
88
|
|
|
89
|
-
| Command | What it does
|
|
90
|
-
| ----------------------------------------- |
|
|
91
|
-
| `geekity serve` | Boot from the config file and listen. The default when no command is given.
|
|
92
|
-
| `geekity init <dir>` | Create a new site in `<dir>`. Refuses a directory that is not empty.
|
|
93
|
-
| `geekity sync` | Rebuild the content index once and exit. Exits non-zero if any file could not be parsed.
|
|
94
|
-
| `geekity rebuild` | Delete `data/geekity.db` and build it again from the files.
|
|
95
|
-
| `geekity resend --all`, `<slug>...` | Send announced posts to every follower and relay again, as they now read. See [Quote posts](#quote-posts).
|
|
96
|
-
| `geekity maintenance on`, `off`, `status` | Take the public site down on purpose with a 503 and `Retry-After`, or bring it back, without a restart. `on --until <time>` names when it should be back.
|
|
97
|
-
| `geekity strip-metadata` | Remove location and camera metadata from files already in `content/uploads`. See [The media library](#the-media-library).
|
|
98
|
-
| `geekity user add <name>` | Create an admin account, so a site can get its first login without the setup screen.
|
|
99
|
-
| `geekity
|
|
100
|
-
| `geekity --help`, `-h` | The same table, on the terminal.
|
|
101
|
-
| `geekity --version` | The installed version.
|
|
102
|
-
|
|
103
|
-
|
|
89
|
+
| Command | What it does |
|
|
90
|
+
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `geekity serve` | Boot from the config file and listen, in a worker the command supervises: Reload on the Plugins screen replaces it, and a crash respawns it. The default when no command is given. |
|
|
92
|
+
| `geekity init <dir>` | Create a new site in `<dir>`. Refuses a directory that is not empty. |
|
|
93
|
+
| `geekity sync` | Rebuild the content index once and exit. Exits non-zero if any file could not be parsed. |
|
|
94
|
+
| `geekity rebuild` | Delete `data/geekity.db` and build it again from the files. |
|
|
95
|
+
| `geekity resend --all`, `<slug>...` | Send announced posts to every follower and relay again, as they now read. See [Quote posts](#quote-posts). |
|
|
96
|
+
| `geekity maintenance on`, `off`, `status` | Take the public site down on purpose with a 503 and `Retry-After`, or bring it back, without a restart. `on --until <time>` names when it should be back. |
|
|
97
|
+
| `geekity strip-metadata` | Remove location and camera metadata from files already in `content/uploads`. See [The media library](#the-media-library). |
|
|
98
|
+
| `geekity user add <name>` | Create an admin account, so a site can get its first login without the setup screen. |
|
|
99
|
+
| `geekity plugin add <package>`, `remove` | Install a plugin package's bundle from the npm registry into the plugins folder (`GEEKITY_PLUGINS_DIR`), or delete it. Reload on the Plugins screen loads the change. |
|
|
100
|
+
| `geekity --help`, `-h` | The same table, on the terminal. |
|
|
101
|
+
| `geekity --version` | The installed version. |
|
|
102
|
+
|
|
103
|
+
An installed plugin may add commands of its own, which `geekity --help` lists
|
|
104
|
+
under _Plugin commands_ and which run whether or not the plugin is enabled.
|
|
105
|
+
That includes a plugin in the plugins folder. The repository README's
|
|
106
|
+
_Plugins_ section describes `geekity plugin add` and writing a plugin.
|
|
107
|
+
|
|
108
|
+
`serve`, `sync`, `rebuild`, `resend`, `maintenance`, `strip-metadata`, `user add`, `plugin` and every plugin command take
|
|
104
109
|
`--config <file>`; without it they look for `geekity.config.ts`, then
|
|
105
110
|
`geekity.config.js`, then `geekity.config.mjs` in the working directory, and run
|
|
106
111
|
on defaults if there is none.
|
|
@@ -291,9 +296,28 @@ directory; absolute ones are used as given.
|
|
|
291
296
|
| `federation` | `{}` | — | Federation stores and guards. See [Federation](#federation). |
|
|
292
297
|
| `commentChecker` | Akismet | — | A spam checker of the site's own, which wins over the key in `data/akismet.json`. See [Akismet](#akismet). |
|
|
293
298
|
| `mail` | `{}` | — | Mail provider, retries, backoff and logger. See [Email](#email). |
|
|
299
|
+
| `plugins` | `[]` | — | The installed plugins. Which run is the `plugins` key of `site.json`, set under Admin > Plugins. A plugin's secret settings take an environment variable each; see below. |
|
|
294
300
|
|
|
295
301
|
Precedence is environment variable, then config file, then default, so a host
|
|
296
|
-
can override anything without editing the site.
|
|
302
|
+
can override anything without editing the site.
|
|
303
|
+
|
|
304
|
+
A plugin's secret settings, such as an API key, can come from the environment
|
|
305
|
+
too, which is how a Docker site keeps a key in `.env`. The variable is the
|
|
306
|
+
plugin's package name, then `__`, then the setting's key, each upper-cased with
|
|
307
|
+
every run of other characters, underscores included, written as one `_`:
|
|
308
|
+
|
|
309
|
+
| Package | Setting | Variable |
|
|
310
|
+
| --------------------- | --------- | ----------------------------- |
|
|
311
|
+
| `@geekity/plugin-llm` | `api_key` | `GEEKITY_PLUGIN_LLM__API_KEY` |
|
|
312
|
+
| `@acme/plugin-llm` | `api_key` | `ACME_PLUGIN_LLM__API_KEY` |
|
|
313
|
+
|
|
314
|
+
A set variable wins over the value stored in
|
|
315
|
+
`data/plugins/<package name>/secrets.json`, and the plugin's screen shows the
|
|
316
|
+
setting as set by the environment, with no box to change it. The screen prints
|
|
317
|
+
each secret's exact variable name. Only plugin variables contain `__`, so none
|
|
318
|
+
can shadow a core one. Two installed packages whose names convert to the same
|
|
319
|
+
prefix, such as `@a/b-c` and `@a-b/c`, both refuse to load, and Admin > Plugins
|
|
320
|
+
names the two. A boolean environment variable
|
|
297
321
|
takes `true`, `1`, `yes` and `on`, or their opposites; anything else is an error
|
|
298
322
|
rather than a silent `false`.
|
|
299
323
|
|
|
@@ -361,20 +385,20 @@ the same directory reads all of it, and everything in it is meant to be public:
|
|
|
361
385
|
|
|
362
386
|
`data/` is private. It is gitignored, and it is the half to copy somewhere safe:
|
|
363
387
|
|
|
364
|
-
| Path
|
|
365
|
-
|
|
|
366
|
-
| `data/users.json`
|
|
367
|
-
| `data/keys/`
|
|
368
|
-
| `data/locations.json`
|
|
369
|
-
| `data/comment-salt`
|
|
370
|
-
| `data/comments/{slug}.json`
|
|
371
|
-
| `data/akismet.json`
|
|
372
|
-
| `data/mail.json`
|
|
373
|
-
| `data/notification-secret`
|
|
374
|
-
| `data/comment-optouts.json`
|
|
375
|
-
| `data/indieauth-tokens.json`
|
|
376
|
-
| `data/notification-digests.json`
|
|
377
|
-
| `data/
|
|
388
|
+
| Path | What it holds |
|
|
389
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
390
|
+
| `data/users.json` | Usernames and argon2id password hashes. Mode `0600`. |
|
|
391
|
+
| `data/keys/` | Each user's actor key pairs as JWK files. Mode `0600`. **Losing these breaks federation.** |
|
|
392
|
+
| `data/locations.json` | Where each post was written, keyed by permalink (decision-29). Mode `0600`. |
|
|
393
|
+
| `data/comment-salt` | What hides commenters' addresses in the published comment files. Mode `0600`. |
|
|
394
|
+
| `data/comments/{slug}.json` | The emails of that post's commenters, and whether each asked to hear about replies, keyed by comment id. Mode `0600`. |
|
|
395
|
+
| `data/akismet.json` | The Akismet key, and what `verify-key` last said about it. Mode `0600`. |
|
|
396
|
+
| `data/mail.json` | The mail credential: a Brevo API key, an SMTP connection, or both. Mode `0600`. |
|
|
397
|
+
| `data/notification-secret` | What signs the one-click links in a notification. Mode `0600`. Losing it kills every link already in an inbox and nothing else. |
|
|
398
|
+
| `data/comment-optouts.json` | The addresses that have unsubscribed from reply notices. Mode `0600`. |
|
|
399
|
+
| `data/indieauth-tokens.json` | A SHA-256 hash of each IndieAuth access and refresh token, with its user, app, scopes and expiry (decision-24). Mode `0600`. |
|
|
400
|
+
| `data/notification-digests.json` | When each user was last sent a digest. Mode `0600`. Losing it sends one digest early and nothing worse. |
|
|
401
|
+
| `data/plugins/<package name>/` | Each plugin's private folder. Its secret settings are in `secrets.json`, mode `0600`; the rest is the plugin's to say. |
|
|
378
402
|
|
|
379
403
|
Three things under `data/` may be deleted whenever the site is stopped, and
|
|
380
404
|
nothing else in either directory may:
|
|
@@ -617,7 +641,8 @@ hand. Pages, drafts, scheduled and trashed posts do not federate, so nobody
|
|
|
617
641
|
can quote them.
|
|
618
642
|
|
|
619
643
|
A quoting server sends a `QuoteRequest` to the post's author. The inbox, and
|
|
620
|
-
|
|
644
|
+
any plugin inbox that hands its deliveries to the site's own handlers, answers
|
|
645
|
+
it:
|
|
621
646
|
|
|
622
647
|
- For a post the site federates, whose quote lives on the requester's own
|
|
623
648
|
server, it stores an approval and replies `Accept` with a
|
|
@@ -756,135 +781,48 @@ actor lists it in `alsoKnownAs` beside the archive and `/@{username}`. Nothing
|
|
|
756
781
|
else moves: `url` is still the archive and the collections are still the
|
|
757
782
|
archive's children, because a peer refetches those.
|
|
758
783
|
|
|
759
|
-
The CMS never mints one and no screen writes one. It arrives with
|
|
760
|
-
import, or is typed into the file by hand; `/admin/users/<id>` shows it
|
|
784
|
+
The CMS never mints one and no screen writes one. It arrives with a plugin's
|
|
785
|
+
import, such as the WordPress plugin's, or is typed into the file by hand; `/admin/users/<id>` shows it
|
|
761
786
|
read-only beside the account, and a value that is not an absolute URL is
|
|
762
787
|
ignored.
|
|
763
788
|
|
|
764
|
-
### WordPress ActivityPub
|
|
789
|
+
### Moving a site from the WordPress ActivityPub plugin
|
|
765
790
|
|
|
766
|
-
A site that moved here from the WordPress ActivityPub plugin
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
about it until it is on. It needs one thing on the user record in
|
|
776
|
-
`data/users.json` besides the stored actor id above: the number WordPress gave
|
|
777
|
-
that person, which is what its paths are built from.
|
|
778
|
-
|
|
779
|
-
```json
|
|
780
|
-
{
|
|
781
|
-
"id": 2,
|
|
782
|
-
"username": "ada",
|
|
783
|
-
"actorId": "https://example.com/?author=2",
|
|
784
|
-
"wordpressActorId": 2
|
|
785
|
-
}
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
`geekity import wordpress-actor` writes both; no screen does. With the switch
|
|
789
|
-
on, those paths are real inbox routes, signature-verified exactly as the
|
|
790
|
-
canonical ones are — an unsigned or badly signed delivery is refused — plus GET
|
|
791
|
-
routes for the actor, its outbox, its followers and its following. What a peer
|
|
792
|
-
reads back is always the canonical document: the same `id`, the same key, and
|
|
793
|
-
the _canonical_ inbox and collections, so a follower refetching the actor at
|
|
794
|
-
the old URL is the follower that learns the new endpoints. A user with no
|
|
795
|
-
`wordpressActorId` is not reachable through any of it.
|
|
796
|
-
|
|
797
|
-
Every one of those paths records the instant it was last asked for, per user,
|
|
798
|
-
in `data/wordpress-activitypub.json` — a file, so a deleted database does not
|
|
799
|
-
forget it — and the settings page lists each path beside the switch with that
|
|
800
|
-
instant, or _Never_. That is what the switch is watched by: once every
|
|
801
|
-
follower's server has refetched the actor, nothing asks any more, and the
|
|
802
|
-
switch can go off. Clearing it takes the paths away on the very next request,
|
|
803
|
-
with no restart.
|
|
804
|
-
|
|
805
|
-
### Moving a site off the WordPress ActivityPub plugin
|
|
806
|
-
|
|
807
|
-
`geekity import wordpress-actor` brings one person across. It writes the three
|
|
808
|
-
things the CMS cannot mint for itself — the RSA key pair the followers have
|
|
809
|
-
cached, the actor id they key the account by, and the followers — and it is a
|
|
810
|
-
command rather than a screen because a stored actor id is identity for the life
|
|
811
|
-
of the account.
|
|
812
|
-
|
|
813
|
-
```sh
|
|
814
|
-
geekity import wordpress-actor ada \
|
|
815
|
-
--actor-id 'https://example.com/?author=2' \
|
|
816
|
-
--wordpress-id 2 \
|
|
817
|
-
--keypair ada.keypair.json
|
|
818
|
-
```
|
|
819
|
-
|
|
820
|
-
It writes `data/keys/ada.rsassa-pkcs1-v1_5.jwk` (and mints the Ed25519 pair
|
|
821
|
-
beside it, which WordPress never had), puts `actorId` and `wordpressActorId` on
|
|
822
|
-
the user in `data/users.json`, and fetches the plugin's public followers
|
|
823
|
-
collection into `content/_data/federation/ada/followers.json`, dereferencing
|
|
824
|
-
each follower for its inbox, shared inbox, handle, name, avatar and profile URL.
|
|
825
|
-
|
|
826
|
-
| Option | What it is |
|
|
827
|
-
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
828
|
-
| `--actor-id <url>` | The id WordPress published, query string and all. The one thing the import cannot work out for itself. |
|
|
829
|
-
| `--wordpress-id <n>` | The WordPress user id, which is the number in the plugin's paths. |
|
|
830
|
-
| `--keypair <file>` | The JSON the plugin's option holds: `{"private_key": …, "public_key": …}`. |
|
|
831
|
-
| `--private-key <file>`, `--public-key <file>` | The two PEMs instead, for a site still on the legacy user meta. The public key is only checked against the private half; it is derived, never stored. |
|
|
832
|
-
| `--followers <url\|file\|none>` | Where the followers come from. Left off, the plugin's own collection on the actor id's origin. |
|
|
833
|
-
| `--force` | Import over a key pair the user already has. |
|
|
834
|
-
|
|
835
|
-
**Exporting the key pair with wp-cli.** The plugin keeps a user's pair in a
|
|
836
|
-
WordPress option named after their login:
|
|
837
|
-
|
|
838
|
-
```sh
|
|
839
|
-
# On the WordPress server. The login, not the user id, is what names the option.
|
|
840
|
-
wp option get activitypub_keypair_for_ada --format=json > ada.keypair.json
|
|
841
|
-
```
|
|
842
|
-
|
|
843
|
-
A site old enough to still be on the plugin's legacy storage has the pair in
|
|
844
|
-
user meta instead, one PEM per key:
|
|
845
|
-
|
|
846
|
-
```sh
|
|
847
|
-
wp user meta get 2 magic_sig_private_key > ada.private.pem
|
|
848
|
-
wp user meta get 2 magic_sig_public_key > ada.public.pem
|
|
849
|
-
```
|
|
850
|
-
|
|
851
|
-
Either export is accepted, and either PEM encoding — `-----BEGIN PRIVATE
|
|
852
|
-
KEY-----` (PKCS#8) or `-----BEGIN RSA PRIVATE KEY-----` (PKCS#1). Treat the
|
|
853
|
-
files the way you would treat a password: the private key is the account.
|
|
854
|
-
|
|
855
|
-
**Running it twice changes nothing**, and says so, which is what makes it safe
|
|
856
|
-
to run again after an instance that was down comes back — a follower already in
|
|
857
|
-
the file keeps its place and its follow time. A follower whose server will not
|
|
858
|
-
answer is listed and skipped rather than failing the import. A user who already
|
|
859
|
-
has a _different_ key pair is refused: importing over one would change that
|
|
860
|
-
person's identity, and every follower has cached the public half of the key
|
|
861
|
-
that is there. `--force` is the way past that, and is only right when you are
|
|
862
|
-
certain the pair being imported is the one the followers hold.
|
|
791
|
+
A site that moved here from the WordPress ActivityPub plugin keeps its people's
|
|
792
|
+
actor ids and its posts' object ids, which are identity and are the CMS's own
|
|
793
|
+
(above). What it needs for a while besides is the plugin's old paths, which
|
|
794
|
+
followers' servers keep delivering to until they refetch the actor, and an
|
|
795
|
+
import that brings each person's key pair and followers across. Both are the
|
|
796
|
+
[`@geekity/plugin-wordpress`](../plugin-wordpress/README.md) package, which a
|
|
797
|
+
site born on the CMS never installs. decision-14's switch is that plugin now:
|
|
798
|
+
where the cutover used to say "turn the switch on", it says install and enable
|
|
799
|
+
the WordPress plugin.
|
|
863
800
|
|
|
864
801
|
#### The cutover, end to end
|
|
865
802
|
|
|
866
|
-
1. **Export, while the WordPress site is still up.**
|
|
867
|
-
|
|
868
|
-
'
|
|
869
|
-
followers.json` — which is worth doing if the old site is going away before
|
|
870
|
-
the import runs. Note the actor id and the numeric actor id off
|
|
803
|
+
1. **Export, while the WordPress site is still up.** Each person's key pair,
|
|
804
|
+
and the followers if the old site is going away before the import runs. The
|
|
805
|
+
plugin's README says how. Note the actor id and the numeric actor id off
|
|
871
806
|
`https://example.com/?author=2`, and bring the content across.
|
|
872
|
-
2. **
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
807
|
+
2. **Install the WordPress plugin and import.** Add `@geekity/plugin-wordpress`
|
|
808
|
+
to the site's dependencies and to `plugins` in its config, then run `geekity import
|
|
809
|
+
wordpress-actor ada --actor-id … --wordpress-id … --keypair ada.keypair.json`.
|
|
810
|
+
The command works before the plugin is enabled. Check the report: every
|
|
811
|
+
follower should be added, and any that were skipped should be re-run once
|
|
812
|
+
their servers answer.
|
|
813
|
+
3. **Enable the WordPress plugin.** Enable it under `/admin/plugins`, then move
|
|
814
|
+
the DNS. Followers' servers go on delivering to
|
|
815
|
+
the plugin's old inbox paths until they next refetch the actor, and the
|
|
816
|
+
plugin is what catches those deliveries.
|
|
880
817
|
4. **Watch.** `/admin/federation` lists the users with their actor ids and
|
|
881
|
-
followers;
|
|
818
|
+
followers; the plugin's own screen under Plugins lists each old path with
|
|
882
819
|
the instant it was last asked for. Deliveries should thin out as each
|
|
883
820
|
follower's server refetches the actor and learns the new endpoints.
|
|
884
|
-
5. **
|
|
885
|
-
rather than days, since a quiet instance refetches rarely —
|
|
886
|
-
The paths go away on the very next request, with nothing restarted,
|
|
887
|
-
something was still using them you can
|
|
821
|
+
5. **Disable it.** Once every path says _Never_ again for long enough — weeks
|
|
822
|
+
rather than days, since a quiet instance refetches rarely — disable the
|
|
823
|
+
plugin. The paths go away on the very next request, with nothing restarted,
|
|
824
|
+
and if something was still using them you can enable it again just as
|
|
825
|
+
quickly.
|
|
888
826
|
|
|
889
827
|
Retiring the stored actor id itself is a separate, optional step by the Move
|
|
890
828
|
protocol, and is not part of the cutover: not every follower's software honours
|
|
@@ -1862,6 +1800,8 @@ shadow the login form.
|
|
|
1862
1800
|
| `/admin/messages/read` | `POST` only. Marks one message read, or unread again. |
|
|
1863
1801
|
| `/admin/messages/delete` | `POST` only. Deletes one message, and its file with it. |
|
|
1864
1802
|
| `/admin/appearance/themes` | Appearance > Themes: the themes on disk. `POST` activates the one named. |
|
|
1803
|
+
| `/admin/plugins` | Plugins: every installed plugin, and Enable and Disable. |
|
|
1804
|
+
| `/admin/plugins/<package name>` | A running plugin's own screen, such as `/admin/plugins/@geekity/plugin-wordpress`. |
|
|
1865
1805
|
| `/admin/tools` | Tools > Content index: what the index holds, and the button that rebuilds it. |
|
|
1866
1806
|
| `/admin/tools/rebuild-index` | `POST` only. Offers the rebuild, then reads every file again on the live site. |
|
|
1867
1807
|
| `/admin/settings` | Settings > General: title, tagline, author, base URL, time zone, language, locale, site icon. |
|
|
@@ -1882,7 +1822,7 @@ shadow the login form.
|
|
|
1882
1822
|
| `/admin/users/notifications/mode` | `POST` only. Sets how often that notice arrives: as they arrive, hourly or daily. |
|
|
1883
1823
|
| `/admin/users/delete` | `POST` only. Deletes the user the form names. |
|
|
1884
1824
|
| `/admin/federation` | The actors, their followers, the inbox log, and per-post delivery. |
|
|
1885
|
-
| `/admin/federation/settings` | Federation > Settings: the relays the site subscribes to
|
|
1825
|
+
| `/admin/federation/settings` | Federation > Settings: the relays the site subscribes to. |
|
|
1886
1826
|
| `/admin/federation/resend` | `POST` only. Sends one post to the followers again, as its file now reads. |
|
|
1887
1827
|
| `/admin/setup` | First run: creates the first admin. Closed once a user exists. |
|
|
1888
1828
|
| `/admin/login` | Username and password. |
|
|
@@ -2022,8 +1962,8 @@ pages under `/admin/settings` — General, Reading, Permalinks, Discussion and
|
|
|
2022
1962
|
Email — each read that file, validate what was typed and write it back; nothing
|
|
2023
1963
|
else remembers a setting, and `data/geekity.db` holds none of them. Federation's
|
|
2024
1964
|
page is a sixth one of exactly the same kind, filed under its own section at
|
|
2025
|
-
`/admin/federation/settings` rather than under Settings, because the relays
|
|
2026
|
-
|
|
1965
|
+
`/admin/federation/settings` rather than under Settings, because the relays are
|
|
1966
|
+
about the section that holds the followers.
|
|
2027
1967
|
|
|
2028
1968
|
Each page saves its own fields and no others, onto the file as re-read inside
|
|
2029
1969
|
the write, so two people saving two different pages at the same moment both
|
|
@@ -2050,14 +1990,10 @@ and every other key it already had is kept, `feedSize` and anything a site put
|
|
|
2050
1990
|
there included. A key it does not carry is the default, and a key of the wrong
|
|
2051
1991
|
type is the default too: a hand-edited `site.json` cannot take the site down.
|
|
2052
1992
|
|
|
2053
|
-
`homepage
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
`/`; the third is the
|
|
2058
|
-
[WordPress ActivityPub compatibility](#wordpress-activitypub-compatibility)
|
|
2059
|
-
switch, absent until somebody turns it on and absent again when they turn it
|
|
2060
|
-
off. A `postsPage` without a `homepage` is
|
|
1993
|
+
`homepage` and `postsPage` are the only two written just when they have a
|
|
1994
|
+
value: the slug of the page served at `/` and the slug of the page whose own
|
|
1995
|
+
URL carries the post listing, absent altogether on a site that shows its latest
|
|
1996
|
+
posts at `/`. A `postsPage` without a `homepage` is
|
|
2061
1997
|
ignored, the listing being at `/` already, and a slug naming no published page
|
|
2062
1998
|
is a site back on its latest posts. See [The front page](#the-front-page).
|
|
2063
1999
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
{% import "components/fields.njk" as field %}
|
|
10
10
|
{% from "components/alert.njk" import alert %}
|
|
11
|
+
{% from "components/badge.njk" import badge %}
|
|
11
12
|
{% from "components/button.njk" import button, buttonLink %}
|
|
12
13
|
{% from "components/card.njk" import card, cardActions %}
|
|
13
14
|
{% from "components/collapse.njk" import collapse %}
|
|
@@ -32,6 +33,44 @@
|
|
|
32
33
|
{% endif %}
|
|
33
34
|
{% endmacro %}
|
|
34
35
|
|
|
36
|
+
{% macro pluginActions(fieldId, buttons) %}
|
|
37
|
+
{% if buttons %}
|
|
38
|
+
<div class="flex flex-col gap-2" data-editor-actions="{{ fieldId }}">
|
|
39
|
+
{% for action in buttons %}
|
|
40
|
+
<div class="flex flex-col items-start gap-1" data-editor-action="{{ action.url }}">
|
|
41
|
+
{{ button(action.label, style='soft', hidden=true) }}
|
|
42
|
+
<p class="text-sm" role="status"></p>
|
|
43
|
+
<div class="flex w-full flex-col gap-2 rounded-box border border-base-300 p-3" role="group" aria-label="{{ action.label }}" hidden>
|
|
44
|
+
<p class="whitespace-pre-wrap break-words"></p>
|
|
45
|
+
<ul class="flex flex-col gap-1" data-editor-choices hidden></ul>
|
|
46
|
+
<template data-editor-choice>
|
|
47
|
+
<li>
|
|
48
|
+
<label class="flex items-start gap-2">
|
|
49
|
+
<input type="checkbox" class="checkbox checkbox-sm mt-0.5" form="editor-suggestions">
|
|
50
|
+
<span class="flex flex-col">
|
|
51
|
+
<span class="flex flex-wrap items-center gap-2">
|
|
52
|
+
<span class="break-all" data-choice-value></span>
|
|
53
|
+
{{ badge('', color='primary', style='soft') }}
|
|
54
|
+
</span>
|
|
55
|
+
<span class="text-sm text-base-content/70" data-choice-note></span>
|
|
56
|
+
</span>
|
|
57
|
+
</label>
|
|
58
|
+
</li>
|
|
59
|
+
</template>
|
|
60
|
+
<template data-editor-choice-group>
|
|
61
|
+
<li class="pt-1 text-sm font-semibold first:pt-0" data-choice-group></li>
|
|
62
|
+
</template>
|
|
63
|
+
<div class="flex gap-2">
|
|
64
|
+
{{ button('Accept', color='primary') }}
|
|
65
|
+
{{ button('Dismiss', style='ghost') }}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
{% endfor %}
|
|
70
|
+
</div>
|
|
71
|
+
{% endif %}
|
|
72
|
+
{% endmacro %}
|
|
73
|
+
|
|
35
74
|
{% block content %}
|
|
36
75
|
<h1 class="mb-6 text-2xl font-bold">{{ heading }}</h1>
|
|
37
76
|
|
|
@@ -72,6 +111,7 @@
|
|
|
72
111
|
{% set titleHint %}{% if kind.type == "post" %}Leave it empty to write a note: the post is then its words, and takes its slug from the first of them.{% endif %}{% endset %}
|
|
73
112
|
{#- A post may go without one, and is then a note; a page is always named. -#}
|
|
74
113
|
{{ field.text('editor-title', 'title', 'Title', value=form.title, required=(kind.type == "page"), autofocus=true, error=(error if errorField == 'editor-title' else ''), hint=titleHint) }}
|
|
114
|
+
{{ pluginActions('editor-title', editorActions['editor-title']) }}
|
|
75
115
|
|
|
76
116
|
{{ field.textarea('editor-body', 'body', 'Body', value=form.body, rows=24) }}
|
|
77
117
|
|
|
@@ -242,6 +282,7 @@
|
|
|
242
282
|
{% call collapse('Tags and categories', open.taxonomy, fields=true) %}
|
|
243
283
|
{% if kind.tagged %}
|
|
244
284
|
{{ field.text('editor-tags', 'tags', 'Tags', value=form.tags, hint='Separated by commas.') }}
|
|
285
|
+
{{ pluginActions('editor-tags', editorActions['editor-tags']) }}
|
|
245
286
|
{% endif %}
|
|
246
287
|
{% if kind.categorised %}
|
|
247
288
|
{{ field.text('editor-categories', 'categories', 'Categories', value=form.categories, hint='Separated by commas.') }}
|
|
@@ -318,6 +359,7 @@
|
|
|
318
359
|
|
|
319
360
|
{% call collapse('Summary and language', open.summary, fields=true) %}
|
|
320
361
|
{{ field.text('editor-description', 'description', 'Description', value=form.description, error=(error if errorField == 'editor-description' else '')) }}
|
|
362
|
+
{{ pluginActions('editor-description', editorActions['editor-description']) }}
|
|
321
363
|
|
|
322
364
|
{#- The `lang` front matter (TASK-154): a post or page written in another
|
|
323
365
|
language than the site's says so, for screen readers, translation
|
|
@@ -367,6 +409,9 @@
|
|
|
367
409
|
{% endcall %}
|
|
368
410
|
</div>
|
|
369
411
|
</form>
|
|
412
|
+
{% if editorActions | length %}
|
|
413
|
+
<form id="editor-suggestions" hidden></form>
|
|
414
|
+
{% endif %}
|
|
370
415
|
|
|
371
416
|
{# CodeMirror, the preview tab and the upload control. A module script is
|
|
372
417
|
deferred by definition, so it runs after the form is in the document.
|
|
@@ -392,4 +437,7 @@
|
|
|
392
437
|
{% if kind.type == 'post' %}
|
|
393
438
|
<script defer src="{{ assetPrefix }}location.js"></script>
|
|
394
439
|
{% endif %}
|
|
440
|
+
{% if editorActions | length %}
|
|
441
|
+
<script defer src="{{ assetPrefix }}editor-actions.js"></script>
|
|
442
|
+
{% endif %}
|
|
395
443
|
{% endblock %}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
{# /admin/federation/settings: which relays boost the site
|
|
2
|
-
still answering the paths a WordPress site arrived with. It is Federation >
|
|
1
|
+
{# /admin/federation/settings: which relays boost the site. It is Federation >
|
|
3
2
|
Settings rather than Settings > Federation (TASK-109): the section holds
|
|
4
3
|
everything about federation, and Followers beside it is where the actors and
|
|
5
4
|
the people following them are. Who the site's people are on the fediverse is
|
|
6
5
|
a user's profile now (decision-14). #}
|
|
7
6
|
{% extends "layouts/settings-page.njk" %}
|
|
8
7
|
{% import "components/fields.njk" as field %}
|
|
9
|
-
{% from "components/card.njk" import card %}
|
|
10
|
-
{% from "components/table.njk" import table %}
|
|
11
8
|
|
|
12
9
|
{% block settingsProblems %}
|
|
13
10
|
{{ field.problem('settings-relays', problems.relays) }}
|
|
@@ -23,59 +20,4 @@
|
|
|
23
20
|
{% endset %}
|
|
24
21
|
{{ field.textarea('settings-relays', fields.relays, 'Relays', form.relays,
|
|
25
22
|
error=problems.relays, hint=relaysHint, rows='3', spellcheck='false', autofocus=true) }}
|
|
26
|
-
|
|
27
|
-
<h2 class="mt-6 text-lg font-semibold">WordPress</h2>
|
|
28
|
-
|
|
29
|
-
{% set wordpressHint %}
|
|
30
|
-
Answer the inbox and collection paths the WordPress ActivityPub plugin
|
|
31
|
-
published, under <code>/wp-json/activitypub/1.0/</code>, for the users
|
|
32
|
-
carrying its numeric actor id. Only a site that moved here from that plugin
|
|
33
|
-
needs this: a follower's server keeps delivering to the inbox URL it cached
|
|
34
|
-
until it next refetches the actor, and this is what catches those
|
|
35
|
-
deliveries in the meantime.
|
|
36
|
-
{% endset %}
|
|
37
|
-
{{ field.checkbox('settings-wordpress-activitypub', fields.wordpressActivityPub,
|
|
38
|
-
'WordPress ActivityPub compatibility', checked=form.wordpressActivityPub,
|
|
39
|
-
hint=wordpressHint) }}
|
|
40
|
-
{% endblock %}
|
|
41
|
-
|
|
42
|
-
{% block settingsPanels %}
|
|
43
|
-
{# When each compatibility path was last asked for. It is the whole reason
|
|
44
|
-
the instants are recorded: once every follower's server has refetched the
|
|
45
|
-
actor, nothing asks any more and the switch can go off. #}
|
|
46
|
-
{% call card(title='WordPress paths') %}
|
|
47
|
-
{% if wordpressRoutes.length %}
|
|
48
|
-
{% call table('WordPress paths', inCard=true) %}
|
|
49
|
-
<thead>
|
|
50
|
-
<tr><th scope="col">Path</th><th scope="col">For</th><th scope="col">Last asked for</th></tr>
|
|
51
|
-
</thead>
|
|
52
|
-
<tbody>
|
|
53
|
-
{% for route in wordpressRoutes %}
|
|
54
|
-
<tr>
|
|
55
|
-
<td><code>{{ route.path }}</code></td>
|
|
56
|
-
<td>{{ route.username or 'every user' }}</td>
|
|
57
|
-
<td class="whitespace-nowrap">
|
|
58
|
-
{% if route.lastRequestedAt %}
|
|
59
|
-
{{ route.lastRequestedAt | date('readable') }}
|
|
60
|
-
{% else %}
|
|
61
|
-
Never
|
|
62
|
-
{% endif %}
|
|
63
|
-
</td>
|
|
64
|
-
</tr>
|
|
65
|
-
{% endfor %}
|
|
66
|
-
</tbody>
|
|
67
|
-
{% endcall %}
|
|
68
|
-
<p class="text-sm">
|
|
69
|
-
Once every one of these has been quiet for long enough that every
|
|
70
|
-
follower's server has refetched the actor, turn the switch off: these
|
|
71
|
-
paths are a cache other servers hold, not this site's identity, and the
|
|
72
|
-
CMS carries them only until the caches have moved on.
|
|
73
|
-
</p>
|
|
74
|
-
{% else %}
|
|
75
|
-
<p class="text-sm">
|
|
76
|
-
No user carries a WordPress actor id, so the switch would serve nothing.
|
|
77
|
-
The <code>geekity import wordpress-actor</code> command sets one.
|
|
78
|
-
</p>
|
|
79
|
-
{% endif %}
|
|
80
|
-
{% endcall %}
|
|
81
23
|
{% endblock %}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{# /admin/plugins: every installed plugin and where it stands (decision-33).
|
|
2
|
+
|
|
3
|
+
Enabling writes the plugins key of content/_data/site.json and takes effect
|
|
4
|
+
on the next request. A plugin's row names each plugin it requires with the
|
|
5
|
+
range it needs, linked to that plugin's row when it is installed, because
|
|
6
|
+
nothing is enabled on the operator's behalf. Under geekity serve, Reload
|
|
7
|
+
appears when the plugins folder differs from what this worker loaded
|
|
8
|
+
(TASK-288). #}
|
|
9
|
+
{% extends "layouts/shell.njk" %}
|
|
10
|
+
{% from "components/badge.njk" import status %}
|
|
11
|
+
{% from "components/button.njk" import button %}
|
|
12
|
+
{% from "components/card.njk" import card, cardActions %}
|
|
13
|
+
|
|
14
|
+
{% block title %}Plugins{% endblock %}
|
|
15
|
+
|
|
16
|
+
{% block content %}
|
|
17
|
+
<h1 class="mb-6 text-2xl font-bold">Plugins</h1>
|
|
18
|
+
|
|
19
|
+
{% if reloaded %}
|
|
20
|
+
<div role="status" class="alert alert-success mb-6"><span>Plugins reloaded.</span></div>
|
|
21
|
+
{% endif %}
|
|
22
|
+
|
|
23
|
+
{% if reloadFailure %}
|
|
24
|
+
<div role="alert" class="alert alert-error mb-6">
|
|
25
|
+
<span>The last reload failed, so this server carried on with the plugins it had. {{ reloadFailure }}</span>
|
|
26
|
+
</div>
|
|
27
|
+
{% endif %}
|
|
28
|
+
|
|
29
|
+
{% if reload %}
|
|
30
|
+
<div id="plugins-reload" class="mb-8 max-w-prose">
|
|
31
|
+
{% call card(title="The plugins folder has changed") %}
|
|
32
|
+
<p>Reload starts a new server with the plugins folder as it is now, then retires this one. No request is turned away.</p>
|
|
33
|
+
<dl class="text-sm">
|
|
34
|
+
{% for heading, names in [["Added", reload.changes.added], ["Removed", reload.changes.removed], ["Updated", reload.changes.updated]] %}
|
|
35
|
+
{% if names.length %}
|
|
36
|
+
<dt class="font-semibold">{{ heading }}:</dt>
|
|
37
|
+
<dd class="mb-2">
|
|
38
|
+
{% for name in names %}<code class="break-all">{{ name }}</code>{% if not loop.last %}, {% endif %}{% endfor %}
|
|
39
|
+
</dd>
|
|
40
|
+
{% endif %}
|
|
41
|
+
{% endfor %}
|
|
42
|
+
</dl>
|
|
43
|
+
<form method="post" action="{{ reload.url }}">
|
|
44
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
45
|
+
{% call cardActions() %}
|
|
46
|
+
{{ button('Reload', type='submit', color='primary') }}
|
|
47
|
+
{% endcall %}
|
|
48
|
+
</form>
|
|
49
|
+
{% endcall %}
|
|
50
|
+
</div>
|
|
51
|
+
{% endif %}
|
|
52
|
+
|
|
53
|
+
{% if not plugins.length %}
|
|
54
|
+
<p class="max-w-prose">
|
|
55
|
+
No plugins are installed. A site with its own <code>server.ts</code> passes them in
|
|
56
|
+
<code>plugins</code> on its config.
|
|
57
|
+
</p>
|
|
58
|
+
{% endif %}
|
|
59
|
+
|
|
60
|
+
<ul class="mb-8 grid grid-cols-1 gap-6 xl:grid-cols-2">
|
|
61
|
+
{% for plugin in plugins %}
|
|
62
|
+
<li id="{{ plugin.anchor }}">
|
|
63
|
+
{% call card(title=plugin.label) %}
|
|
64
|
+
<p class="flex flex-wrap items-center gap-2">
|
|
65
|
+
<code class="break-all">{{ plugin.name }}</code>
|
|
66
|
+
<span>{{ plugin.version }}</span>
|
|
67
|
+
{{ status(plugin.badge.status, plugin.badge.label) }}
|
|
68
|
+
</p>
|
|
69
|
+
<p>{{ plugin.description }}</p>
|
|
70
|
+
<p class="text-sm">From {{ plugin.source }}.</p>
|
|
71
|
+
|
|
72
|
+
{% if plugin.problem %}
|
|
73
|
+
<p class="text-sm text-error">{{ plugin.problem }}</p>
|
|
74
|
+
{% endif %}
|
|
75
|
+
|
|
76
|
+
{% if plugin.requirements.length %}
|
|
77
|
+
<div class="text-sm">
|
|
78
|
+
<p>Requires:</p>
|
|
79
|
+
<ul class="list-disc ps-5">
|
|
80
|
+
{% for requirement in plugin.requirements %}
|
|
81
|
+
<li>
|
|
82
|
+
{% if requirement.satisfied %}
|
|
83
|
+
<code>{{ requirement.name }}</code> <code>{{ requirement.range }}</code>, which is enabled.
|
|
84
|
+
{% else %}
|
|
85
|
+
Needs <code>{{ requirement.name }}</code> <code>{{ requirement.range }}</code>, which is {{ requirement.words }}.
|
|
86
|
+
{% if requirement.anchor %}
|
|
87
|
+
<a class="link" href="#{{ requirement.anchor }}">Go to it</a>.
|
|
88
|
+
{% endif %}
|
|
89
|
+
{% endif %}
|
|
90
|
+
</li>
|
|
91
|
+
{% endfor %}
|
|
92
|
+
</ul>
|
|
93
|
+
</div>
|
|
94
|
+
{% endif %}
|
|
95
|
+
|
|
96
|
+
{% if plugin.canEnable or plugin.canDisable %}
|
|
97
|
+
<form method="post" action="{{ enableUrl if plugin.canEnable else disableUrl }}">
|
|
98
|
+
<input type="hidden" name="csrf_token" value="{{ csrfToken }}" />
|
|
99
|
+
<input type="hidden" name="{{ field }}" value="{{ plugin.name }}" />
|
|
100
|
+
{% call cardActions() %}
|
|
101
|
+
{% if plugin.canEnable %}
|
|
102
|
+
{{ button('Enable', type='submit', color='primary') }}
|
|
103
|
+
{% else %}
|
|
104
|
+
{{ button('Disable', type='submit') }}
|
|
105
|
+
{% endif %}
|
|
106
|
+
{% endcall %}
|
|
107
|
+
</form>
|
|
108
|
+
{% endif %}
|
|
109
|
+
{% endcall %}
|
|
110
|
+
</li>
|
|
111
|
+
{% endfor %}
|
|
112
|
+
</ul>
|
|
113
|
+
{% endblock %}
|