@coywolf/emdash 0.0.0-stage → 0.35.14
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/LICENSE +21 -0
- package/README.md +203 -2
- package/docs/README.md +45 -0
- package/docs/ai.md +39 -0
- package/docs/backups.md +88 -0
- package/docs/breadcrumbs.md +59 -0
- package/docs/code-blocks.md +21 -0
- package/docs/content-urls.md +41 -0
- package/docs/custom-blocks.md +27 -0
- package/docs/discovery.md +32 -0
- package/docs/files.md +44 -0
- package/docs/form-uploads.md +59 -0
- package/docs/headings.md +37 -0
- package/docs/images.md +141 -0
- package/docs/internals.md +13 -0
- package/docs/links.md +34 -0
- package/docs/performance.md +72 -0
- package/docs/redirects.md +84 -0
- package/docs/releasing.md +21 -0
- package/docs/reviews.md +89 -0
- package/docs/robots.md +81 -0
- package/docs/schema.md +79 -0
- package/docs/search.md +114 -0
- package/docs/videos.md +48 -0
- package/docs/wordpress-import.md +198 -0
- package/package.json +79 -5
- package/src/admin/admin-css.ts +57 -0
- package/src/admin/ai.tsx +915 -0
- package/src/admin/backups.tsx +613 -0
- package/src/admin/breadcrumbs.tsx +542 -0
- package/src/admin/codeBlocks.tsx +204 -0
- package/src/admin/customBlocks.tsx +321 -0
- package/src/admin/dirty.ts +20 -0
- package/src/admin/discovery.tsx +463 -0
- package/src/admin/download.ts +28 -0
- package/src/admin/feature-switches.tsx +124 -0
- package/src/admin/features.tsx +142 -0
- package/src/admin/files.tsx +791 -0
- package/src/admin/formUploads.tsx +445 -0
- package/src/admin/guides.tsx +18 -0
- package/src/admin/headings.tsx +262 -0
- package/src/admin/images.tsx +825 -0
- package/src/admin/index.tsx +62 -0
- package/src/admin/links.tsx +980 -0
- package/src/admin/performance.tsx +330 -0
- package/src/admin/preview.tsx +63 -0
- package/src/admin/redirects-removed.tsx +266 -0
- package/src/admin/redirects.tsx +470 -0
- package/src/admin/reviews.tsx +232 -0
- package/src/admin/robots-bots.tsx +647 -0
- package/src/admin/robots-shared.tsx +109 -0
- package/src/admin/robots-wizard.tsx +748 -0
- package/src/admin/robots.tsx +911 -0
- package/src/admin/save-bar.tsx +134 -0
- package/src/admin/schema.tsx +1187 -0
- package/src/admin/search.tsx +355 -0
- package/src/admin/settings-ui.tsx +87 -0
- package/src/admin/trash-prompt-core.ts +135 -0
- package/src/admin/trash-prompt.tsx +156 -0
- package/src/admin/usage.tsx +301 -0
- package/src/admin/videos-display.tsx +248 -0
- package/src/admin/videos.tsx +1202 -0
- package/src/admin/wpImport.tsx +1516 -0
- package/src/ai/entities.ts +59 -0
- package/src/ai/export.ts +61 -0
- package/src/ai/logic.ts +606 -0
- package/src/ai/pack.ts +121 -0
- package/src/ai/prompts.ts +87 -0
- package/src/ai/providers.ts +276 -0
- package/src/ai/routes.ts +359 -0
- package/src/ai/store.ts +301 -0
- package/src/ai/wikidata.ts +87 -0
- package/src/ai/worker.ts +530 -0
- package/src/astro/astro-modules.d.ts +6 -0
- package/src/astro/breadcrumbs/Breadcrumbs.astro +129 -0
- package/src/astro/breadcrumbs/BreadcrumbsBlock.astro +12 -0
- package/src/astro/codeBlocks/CodeBlock.astro +62 -0
- package/src/astro/codeBlocks/EmDashCode.astro +61 -0
- package/src/astro/customBlocks/CustomBlock.astro +64 -0
- package/src/astro/customBlocks/Details.astro +8 -0
- package/src/astro/customBlocks/Disclosure.astro +8 -0
- package/src/astro/customBlocks/Note.astro +8 -0
- package/src/astro/customBlocks/Podcast.astro +8 -0
- package/src/astro/customBlocks/Quote.astro +8 -0
- package/src/astro/customBlocks/Testimonial.astro +8 -0
- package/src/astro/files/FileCard.astro +103 -0
- package/src/astro/headings/HeadingBlock.astro +129 -0
- package/src/astro/headings/TableOfContents.astro +45 -0
- package/src/astro/headings/request.ts +19 -0
- package/src/astro/images/ResponsiveImage.astro +60 -0
- package/src/astro/index.ts +84 -0
- package/src/astro/reviews/CoywolfReview.astro +27 -0
- package/src/astro/reviews/Review.astro +49 -0
- package/src/astro/search/SearchBox.astro +61 -0
- package/src/astro/search/SearchBoxForm.astro +509 -0
- package/src/astro/tables/CaptionedTable.astro +72 -0
- package/src/astro/videos/CoywolfVideo.astro +448 -0
- package/src/backups/download.ts +112 -0
- package/src/backups/dump.ts +288 -0
- package/src/backups/module.ts +276 -0
- package/src/backups/pack.ts +33 -0
- package/src/backups/restore.ts +209 -0
- package/src/backups/store.ts +409 -0
- package/src/breadcrumbs/pack.ts +113 -0
- package/src/breadcrumbs/settings.ts +102 -0
- package/src/breadcrumbs/stamp.ts +40 -0
- package/src/breadcrumbs/trail.ts +123 -0
- package/src/codeBlocks/contrast.ts +179 -0
- package/src/codeBlocks/hljs.d.ts +5 -0
- package/src/codeBlocks/pack.ts +110 -0
- package/src/codeBlocks/render.ts +304 -0
- package/src/codeBlocks/settings.ts +18 -0
- package/src/codeBlocks/themes.generated.ts +304 -0
- package/src/codeBlocks/themes.ts +114 -0
- package/src/core/compose.ts +244 -0
- package/src/core/content-url.ts +888 -0
- package/src/core/csv.ts +119 -0
- package/src/core/d1-batch.ts +65 -0
- package/src/core/entry-docs.ts +175 -0
- package/src/core/features-module.ts +48 -0
- package/src/core/features.ts +392 -0
- package/src/core/generation.ts +88 -0
- package/src/core/guide-content.ts +166 -0
- package/src/core/module.ts +104 -0
- package/src/core/read-failures.ts +57 -0
- package/src/core/request-memo.ts +71 -0
- package/src/core/secrets.ts +83 -0
- package/src/core/settings-enhance.ts +74 -0
- package/src/core/site.ts +36 -0
- package/src/core/storage.ts +27 -0
- package/src/customBlocks/pack.ts +260 -0
- package/src/customBlocks/render.ts +424 -0
- package/src/customBlocks/rich.ts +313 -0
- package/src/customBlocks/settings.ts +46 -0
- package/src/discovery/build.ts +252 -0
- package/src/discovery/feed-build.ts +217 -0
- package/src/discovery/feed.ts +172 -0
- package/src/discovery/indexnow.ts +114 -0
- package/src/discovery/llms.ts +79 -0
- package/src/discovery/markdown.ts +294 -0
- package/src/discovery/memory-cache.ts +97 -0
- package/src/discovery/middleware.ts +186 -0
- package/src/discovery/module.ts +353 -0
- package/src/discovery/news.ts +99 -0
- package/src/discovery/pack.ts +58 -0
- package/src/discovery/settings.ts +91 -0
- package/src/discovery/urls.ts +35 -0
- package/src/env.d.ts +10 -0
- package/src/files/card.ts +231 -0
- package/src/files/counts.ts +60 -0
- package/src/files/export.ts +44 -0
- package/src/files/format.ts +163 -0
- package/src/files/module.ts +596 -0
- package/src/files/pack.ts +92 -0
- package/src/files/r2-rules.ts +41 -0
- package/src/files/range.ts +50 -0
- package/src/files/s3.ts +114 -0
- package/src/files/serve.ts +141 -0
- package/src/files/sigv4.ts +160 -0
- package/src/files/site.ts +137 -0
- package/src/files/walker.ts +58 -0
- package/src/formUploads/lib.ts +266 -0
- package/src/formUploads/pack.ts +55 -0
- package/src/formUploads/wrap.ts +592 -0
- package/src/forms.ts +16 -0
- package/src/headings/pack.ts +154 -0
- package/src/headings/render.ts +139 -0
- package/src/headings/settings.ts +117 -0
- package/src/headings/slug.ts +68 -0
- package/src/headings/stamp.ts +142 -0
- package/src/headings/test-hooks.mjs +14 -0
- package/src/headings/toc.ts +48 -0
- package/src/images/cloudflare.ts +329 -0
- package/src/images/lib.ts +221 -0
- package/src/images/module.ts +372 -0
- package/src/images/pack.ts +804 -0
- package/src/images/pending.ts +37 -0
- package/src/images/settings.ts +48 -0
- package/src/images/setup.ts +192 -0
- package/src/images/variants-estimate.ts +162 -0
- package/src/images/variants-store.ts +475 -0
- package/src/images/variants.ts +311 -0
- package/src/index.ts +143 -0
- package/src/links/check.ts +101 -0
- package/src/links/classify.ts +270 -0
- package/src/links/export.ts +59 -0
- package/src/links/module.ts +652 -0
- package/src/links/pack.ts +43 -0
- package/src/links/pt.ts +325 -0
- package/src/links/store.ts +704 -0
- package/src/middleware.ts +287 -0
- package/src/modules.ts +53 -0
- package/src/options.ts +66 -0
- package/src/pageCache/lib.ts +274 -0
- package/src/pageCache/pack.ts +240 -0
- package/src/pageCache/usage-core.ts +152 -0
- package/src/pageCache/usage.ts +262 -0
- package/src/pageCache/warm.ts +722 -0
- package/src/redirects/middleware.ts +169 -0
- package/src/redirects/module.ts +124 -0
- package/src/redirects/pack.ts +89 -0
- package/src/redirects/removed-core.ts +151 -0
- package/src/redirects/removed.ts +293 -0
- package/src/redirects/rules.ts +355 -0
- package/src/redirects/slug-changes.ts +207 -0
- package/src/redirects/slug-core.ts +49 -0
- package/src/redirects/transfer.ts +92 -0
- package/src/reviews/lib.ts +624 -0
- package/src/reviews/pack.ts +135 -0
- package/src/reviews/schema.ts +128 -0
- package/src/reviews/settings.ts +26 -0
- package/src/robots/bots.ts +321 -0
- package/src/robots/data/bots.json +696 -0
- package/src/robots/data/verified.json +582 -0
- package/src/robots/directory.ts +26 -0
- package/src/robots/explain.ts +191 -0
- package/src/robots/guided.ts +300 -0
- package/src/robots/importer.ts +263 -0
- package/src/robots/middleware.ts +151 -0
- package/src/robots/module.ts +446 -0
- package/src/robots/pack.ts +69 -0
- package/src/robots/radar.ts +129 -0
- package/src/robots/rep.ts +362 -0
- package/src/robots/rules.ts +883 -0
- package/src/robots/transfer.ts +45 -0
- package/src/robots/validate.ts +524 -0
- package/src/schema/catalog.ts +146 -0
- package/src/schema/graph.ts +791 -0
- package/src/schema/module.ts +1138 -0
- package/src/schema/og-image.ts +100 -0
- package/src/schema/pack.ts +58 -0
- package/src/search/engine.ts +578 -0
- package/src/search/fallback.ts +80 -0
- package/src/search/live-cache.ts +116 -0
- package/src/search/live-client.ts +770 -0
- package/src/search/live-serve.ts +282 -0
- package/src/search/module.ts +261 -0
- package/src/search/pack.ts +57 -0
- package/src/search/query.ts +132 -0
- package/src/search/ratelimit-core.ts +118 -0
- package/src/search/ratelimit.ts +79 -0
- package/src/search/snippet.ts +135 -0
- package/src/search/title-index.ts +139 -0
- package/src/search/title-match.ts +192 -0
- package/src/shared.ts +71 -0
- package/src/version.ts +2 -0
- package/src/videos/date.ts +40 -0
- package/src/videos/engagement.ts +75 -0
- package/src/videos/lib.ts +679 -0
- package/src/videos/module.ts +1160 -0
- package/src/videos/pack.ts +187 -0
- package/src/videos/pause.ts +42 -0
- package/src/videos/poster.ts +324 -0
- package/src/videos/render.ts +336 -0
- package/src/videos/store.ts +551 -0
- package/src/videos/stream.ts +134 -0
- package/src/wpImport/convert.ts +702 -0
- package/src/wpImport/guests.ts +329 -0
- package/src/wpImport/gutenberg.ts +157 -0
- package/src/wpImport/markers.ts +66 -0
- package/src/wpImport/pack.ts +451 -0
- package/src/wpImport/parents.ts +202 -0
- package/src/wpImport/prepare.ts +592 -0
- package/src/wpImport/redirects.ts +358 -0
- package/src/wpImport/sources.ts +67 -0
- package/src/wpImport/stream.ts +160 -0
- package/src/wpImport/urls.ts +354 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Coywolf LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,204 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Coywolf Pack for EmDash
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Coywolf Pack is an [EmDash](https://emdashcms.com) plugin that enhances existing features and adds features not available in EmDash. It helps you manage your site like a pro and ensure it's absurdly fast for humans and bots.
|
|
4
|
+
|
|
5
|
+
- **Absurdly fast for humans and bots:** cached pages answer in 60–90 ms, cache warming keeps pages cached for visitors and crawlers alike, and images come straight from Cloudflare's edge.
|
|
6
|
+
- **Enhances what EmDash already does:** redirects, search, robots.txt, schema, images and RSS feeds go well beyond EmDash's built-in versions and starter templates.
|
|
7
|
+
- **Adds what EmDash doesn't have:** backups and restore, link checking, videos, reviews, AI enrichment and more.
|
|
8
|
+
- **Manage your site like a pro:** turn each feature on or off, see your Cloudflare usage and estimated bill, and finish a move from WordPress. Everything starts off, so installing or updating changes nothing until you're ready.
|
|
9
|
+
- **Free and open source** (MIT).
|
|
10
|
+
|
|
11
|
+
**New here?** Follow [Getting started](#getting-started) to install it and turn features on.
|
|
12
|
+
|
|
13
|
+
## Modules
|
|
14
|
+
|
|
15
|
+
Every module starts off. Turn on only the ones you want under **Plugins → Coywolf Pack → Features**.
|
|
16
|
+
|
|
17
|
+
| Module | What it does |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| [Performance](docs/performance.md) | Always on: keeps Cloudflare's edge page cache correct (cleared after deploys and edits), optional cache warming, and a Cloudflare usage card with the estimated bill |
|
|
20
|
+
| [Clean Image URLs](docs/images.md) | Images from a `media.` subdomain on R2, resized to the sizes the theme uses, cached for a year |
|
|
21
|
+
| [Backups](docs/backups.md) | Backups of the D1 database and R2 media, rewind with undo, restore to a new database |
|
|
22
|
+
| [Redirects](docs/redirects.md) | External, file-path and regex redirects, bulk import and export, and automatic redirects for slug changes and removed content |
|
|
23
|
+
| [Schema & Social](docs/schema.md) | One Schema.org graph per page (publisher, articles, authors), breadcrumbs, robots directives, Open Graph |
|
|
24
|
+
| [Discovery](docs/discovery.md) | IndexNow, an RSS feed, a Google News sitemap, and llms.txt with Markdown versions of entries |
|
|
25
|
+
| [Robots.txt Rules](docs/robots.md) | Plain-English robots.txt rules with a guided editor, a verified crawler directory, history and a URL tester |
|
|
26
|
+
| [Search](docs/search.md) | Fast as-you-type results on EmDash's full-text search, a search box, and rate limiting |
|
|
27
|
+
| [Link Manager](docs/links.md) | Every link in your content with its status, and bulk replace, unlink and ignore |
|
|
28
|
+
| [Headings & TOC](docs/headings.md) | Linkable headings and a Table of Contents block |
|
|
29
|
+
| [Breadcrumb Nav](docs/breadcrumbs.md) | An accessible breadcrumb trail for themes and as a block |
|
|
30
|
+
| [Code Blocks](docs/code-blocks.md) | Server-side syntax highlighting, themes, language labels, copy buttons |
|
|
31
|
+
| [File Downloads](docs/files.md) | Download cards, stable download URLs with counts, uploads of any size |
|
|
32
|
+
| [Private form uploads](docs/form-uploads.md) | Files sent through EmDash Forms go to a private bucket that only admins can read |
|
|
33
|
+
| [Videos](docs/videos.md) | Cloudflare Stream library, a video block with a light embed, video schema and sitemap, plays and likes |
|
|
34
|
+
| [Reviews](docs/reviews.md) | A review block (rating, pros and cons) with Review schema |
|
|
35
|
+
| [Custom Blocks](docs/custom-blocks.md) | Note, Details, Affiliate disclosure, Quote, Testimonial and Podcast links blocks |
|
|
36
|
+
| [AI Enrichment](docs/ai.md) | Wikidata-grounded entities for schema, meta descriptions and image alt text |
|
|
37
|
+
| [WordPress import](docs/wordpress-import.md) | Finishes a move from WordPress: blocks, authors, category parents, media URLs and redirects |
|
|
38
|
+
|
|
39
|
+
## Getting started
|
|
40
|
+
|
|
41
|
+
Coywolf Pack is a native (trusted) EmDash plugin: you add it to your site's code and deploy, rather than installing it from EmDash's plugin directory (which lists only sandboxed plugins, and those can't read the database or buckets directly). The steps below use coywolf.com as the example: change the names to your site's.
|
|
42
|
+
|
|
43
|
+
### What you need
|
|
44
|
+
|
|
45
|
+
- An EmDash site (1.2 or later) on Cloudflare Workers, using EmDash's Cloudflare adapter with a D1 database bound as `DB` and an R2 media bucket bound as `MEDIA`. That's the setup EmDash's own Cloudflare starter creates.
|
|
46
|
+
- The site's code in a folder (or Git repository) you can edit and deploy, with Node.js and Wrangler.
|
|
47
|
+
|
|
48
|
+
### 1. Install the package
|
|
49
|
+
|
|
50
|
+
From the site's folder, install the latest release. Releases are listed under [Tags](https://github.com/coywolf-llc/coywolf-pack/tags); replace `v0.35.13` with the newest one:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install https://codeload.github.com/coywolf-llc/coywolf-pack/tar.gz/refs/tags/v0.35.13
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The package is named `@coywolf/emdash`. Use the tarball URL rather than `github:coywolf-llc/coywolf-pack`: npm records `github:` installs as SSH Git URLs, which CI runners without an SSH key can't fetch.
|
|
57
|
+
|
|
58
|
+
### 2. Add the plugin to `astro.config.mjs`
|
|
59
|
+
|
|
60
|
+
Import `coywolfPlugin` and add it to EmDash's `plugins`:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// astro.config.mjs
|
|
64
|
+
import { coywolfPlugin } from "@coywolf/emdash";
|
|
65
|
+
|
|
66
|
+
export default defineConfig({
|
|
67
|
+
// ...
|
|
68
|
+
integrations: [
|
|
69
|
+
emdash({
|
|
70
|
+
// ...your existing database and storage settings
|
|
71
|
+
plugins: [
|
|
72
|
+
coywolfPlugin({
|
|
73
|
+
// Backups is the one module that's added only when you give it a name (it names the backup files).
|
|
74
|
+
backups: { name: "coywolf" },
|
|
75
|
+
}),
|
|
76
|
+
],
|
|
77
|
+
}),
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`coywolfPlugin()` with no options works too: every module except Backups is included with its defaults. Each module's page in [docs](docs/README.md) lists its options; set a module to `false` (for example `videos: false`) to leave it out of the site entirely.
|
|
83
|
+
|
|
84
|
+
### 3. Add the middleware
|
|
85
|
+
|
|
86
|
+
Several features answer their own URLs before EmDash renders a page: redirects, `/robots.txt` rules, the RSS feed, llms.txt and Markdown versions, file downloads, sitemaps for videos and news, image sizes, and cache warming. They need the pack's middleware, first in `src/middleware.ts`:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// src/middleware.ts
|
|
90
|
+
import { sequence } from "astro:middleware";
|
|
91
|
+
import { coywolfPack } from "@coywolf/emdash/middleware";
|
|
92
|
+
|
|
93
|
+
export const onRequest = sequence(coywolfPack() /* , your own middleware */);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If the site has no `src/middleware.ts` yet, create it with just these lines.
|
|
97
|
+
|
|
98
|
+
### 4. Check `wrangler.jsonc`
|
|
99
|
+
|
|
100
|
+
`DB` and `MEDIA` are all the pack needs to start. Other bindings turn on more features; add them when you turn those features on (each module's page in [docs](docs/README.md) has the details):
|
|
101
|
+
|
|
102
|
+
```jsonc
|
|
103
|
+
// wrangler.jsonc (coywolf.com)
|
|
104
|
+
{
|
|
105
|
+
"name": "coywolf",
|
|
106
|
+
"d1_databases": [{ "binding": "DB", "database_name": "coywolf", "database_id": "<D1 database id>" }],
|
|
107
|
+
"r2_buckets": [
|
|
108
|
+
{ "binding": "MEDIA", "bucket_name": "coywolf-media" },
|
|
109
|
+
// Backups: a second bucket for backup files.
|
|
110
|
+
{ "binding": "BACKUPS", "bucket_name": "coywolf-backups" }
|
|
111
|
+
],
|
|
112
|
+
// Page cache: Workers Cache in front of the Worker, and the deployed version so old pages are cleared after a deploy.
|
|
113
|
+
"cache": { "enabled": true },
|
|
114
|
+
"version_metadata": { "binding": "CF_VERSION_METADATA" },
|
|
115
|
+
// Cache warming and checking internal links: the Worker calling itself.
|
|
116
|
+
"services": [{ "binding": "SELF", "service": "coywolf" }]
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
API keys and tokens (for AI Enrichment, Videos, Robots.txt's Cloudflare Radar sync, Clean Image URLs setup, Cloudflare usage) are stored encrypted, so the site also needs EmDash's `EMDASH_ENCRYPTION_KEY` secret. EmDash's setup usually creates it; if not:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npx wrangler secret put EMDASH_ENCRYPTION_KEY
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Keep that key in a password manager: encrypted settings can't be read without it.
|
|
127
|
+
|
|
128
|
+
### 5. Deploy
|
|
129
|
+
|
|
130
|
+
Deploy the site the way you usually do (for example `npx wrangler deploy`, or your CI). Nothing changes for visitors yet: every feature starts off.
|
|
131
|
+
|
|
132
|
+
### 6. Turn features on
|
|
133
|
+
|
|
134
|
+
1. Sign in to the site's admin (`https://coywolf.com/_emdash/admin`).
|
|
135
|
+
2. Open **Plugins → Coywolf Pack → Features**.
|
|
136
|
+
3. Turn on the modules you want, and any of their parts. Each module that's on adds its page to the **Coywolf Pack** section of the sidebar.
|
|
137
|
+
4. Open each module's page for its settings and step-by-step guides. A part can also be turned on or off there.
|
|
138
|
+
|
|
139
|
+
A good first set for most sites:
|
|
140
|
+
|
|
141
|
+
| Turn on | What you get | Then |
|
|
142
|
+
| --- | --- | --- |
|
|
143
|
+
| **Performance** (always on) | Page cache lifetimes, clearing after deploys, cache warming | Turn on **Cache warming** on the Performance page (needs `SELF`) |
|
|
144
|
+
| **Clean Image URLs** | Images from a `media.` subdomain, resized and cached | Run **Set up media host** on its page (see [Setting up a media host](docs/images.md#setting-up-a-media-host)) |
|
|
145
|
+
| **Schema & Social** | Schema.org markup and Open Graph tags | Review the publisher on the Schema page |
|
|
146
|
+
| **Redirects** | Redirects EmDash's own can't handle, plus slug-change and removed-content redirects | Import old redirects on the Redirects page |
|
|
147
|
+
| **Discovery** | IndexNow, an RSS feed, a news sitemap, llms.txt | Turn on the parts you want on the Discovery page |
|
|
148
|
+
| **Backups** | Backups of the database and media, on demand or daily | Add `backups: { name: "coywolf" }` (step 2) and the `BACKUPS` bucket, then turn on the daily backup (see [Backups](docs/backups.md)) |
|
|
149
|
+
|
|
150
|
+
### Using the admin
|
|
151
|
+
|
|
152
|
+
- **Features** (**Plugins → Coywolf Pack → Features**) turns modules and their parts on or off. A module's page also has switches for its parts.
|
|
153
|
+
- **Each module's page** holds its settings, grouped into sections, with step-by-step guides. A feature that needs an API key or a binding shows a setup card until it's there.
|
|
154
|
+
- **API keys and tokens** can be entered on their module's page or on the plugin's **Settings** page, which lists only those. EmDash stores them encrypted.
|
|
155
|
+
- **Saving:** a bar appears at the bottom of a settings page when there are unsaved changes, with **Discard** and **Save** (also Ctrl/⌘+S).
|
|
156
|
+
|
|
157
|
+
### Updating
|
|
158
|
+
|
|
159
|
+
Install the newer release's tarball (step 1 with the new tag), then deploy. Updates never turn features on by themselves: new features start off.
|
|
160
|
+
|
|
161
|
+
### Local development
|
|
162
|
+
|
|
163
|
+
In development, keep Vite from pre-bundling the plugin. A pre-bundled copy gets its own instance of `emdash`, which breaks plugin error handling. Its UI libraries should stay pre-bundled:
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
// astro.config.mjs
|
|
167
|
+
export default defineConfig({
|
|
168
|
+
vite: {
|
|
169
|
+
optimizeDeps: { exclude: ["@coywolf/emdash"], include: ["@phosphor-icons/react", "@cloudflare/kumo"] },
|
|
170
|
+
ssr: { optimizeDeps: { exclude: ["@coywolf/emdash"] } },
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Troubleshooting
|
|
176
|
+
|
|
177
|
+
- **Coywolf Pack isn't in the admin.** Check that `coywolfPlugin()` is in `emdash({ plugins: [...] })` in `astro.config.mjs` and that the site was deployed (or the dev server restarted) after adding it.
|
|
178
|
+
- **A module's page isn't in the sidebar.** Its main switch is off: turn it on under **Plugins → Coywolf Pack → Features**.
|
|
179
|
+
- **Redirects, `/robots.txt` rules, the feed or downloads don't answer.** The middleware isn't running: check step 3, and that `coywolfPack()` comes before your own middleware in `sequence()`.
|
|
180
|
+
- **A feature shows a setup card instead of its settings.** It needs a binding, an API key or `EMDASH_ENCRYPTION_KEY`; the card says which.
|
|
181
|
+
- **Pages don't change after an edit.** EmDash and the pack clear cached pages on edits and deploys; **Plugins → Coywolf Pack → Performance → Clear pages and images** clears everything now. Cloudflare's zone **Purge Everything** doesn't reach the Worker's page cache.
|
|
182
|
+
|
|
183
|
+
## Documentation
|
|
184
|
+
|
|
185
|
+
Each module has its own page in [docs](docs/README.md), with its setup, settings and notes:
|
|
186
|
+
|
|
187
|
+
- [Performance](docs/performance.md): page cache, cache warming, Cloudflare usage
|
|
188
|
+
- [Content URLs](docs/content-urls.md): telling the pack how your theme routes entries
|
|
189
|
+
- Modules: [Clean Image URLs](docs/images.md) · [Backups](docs/backups.md) · [Redirects](docs/redirects.md) · [Schema & Social](docs/schema.md) · [Discovery](docs/discovery.md) · [Robots.txt Rules](docs/robots.md) · [Search](docs/search.md) · [Link Manager](docs/links.md) · [Headings & TOC](docs/headings.md) · [Breadcrumb Nav](docs/breadcrumbs.md) · [Code Blocks](docs/code-blocks.md) · [File Downloads](docs/files.md) · [Private form uploads](docs/form-uploads.md) · [Videos](docs/videos.md) · [Reviews](docs/reviews.md) · [Custom Blocks](docs/custom-blocks.md) · [AI Enrichment](docs/ai.md)
|
|
190
|
+
- [Migrating from WordPress](docs/wordpress-import.md)
|
|
191
|
+
- [Internals](docs/internals.md): how the pack keeps database reads low
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
Releases are tagged, published on GitHub and on npm: see [Releasing](docs/releasing.md).
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npx tsc --noEmit -p . # typecheck
|
|
199
|
+
npm test # unit tests: test/ and src/**/*.test.ts (Node 22.15+; runs the TypeScript sources directly)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Coywolf Pack documentation
|
|
2
|
+
|
|
3
|
+
Coywolf Pack is an [EmDash](https://emdashcms.com) plugin that enhances existing features and adds features not available in EmDash. It helps you manage your site like a pro and ensure it's absurdly fast for humans and bots.
|
|
4
|
+
|
|
5
|
+
- **Absurdly fast for humans and bots:** cached pages answer in 60–90 ms, cache warming keeps pages cached for visitors and crawlers alike, and images come straight from Cloudflare's edge.
|
|
6
|
+
- **Enhances what EmDash already does:** redirects, search, robots.txt, schema, images and RSS feeds go well beyond EmDash's built-in versions and starter templates.
|
|
7
|
+
- **Adds what EmDash doesn't have:** backups and restore, link checking, videos, reviews, AI enrichment and more.
|
|
8
|
+
- **Manage your site like a pro:** turn each feature on or off, see your Cloudflare usage and estimated bill, and finish a move from WordPress. Everything starts off, so installing or updating changes nothing until you're ready.
|
|
9
|
+
- **Free and open source** (MIT).
|
|
10
|
+
|
|
11
|
+
Start with [Getting started](../README.md#getting-started) to install the pack and turn features on.
|
|
12
|
+
|
|
13
|
+
## Site-wide
|
|
14
|
+
|
|
15
|
+
- [Performance](performance.md): Workers Cache, clearing after deploys and edits, cache warming, Cloudflare usage
|
|
16
|
+
- [Content URLs](content-urls.md): telling the pack how your theme routes entries (category paths, nested pages)
|
|
17
|
+
- [Internals](internals.md): how the pack keeps D1 reads to about one round trip per uncached page
|
|
18
|
+
|
|
19
|
+
## Modules
|
|
20
|
+
|
|
21
|
+
- [Clean Image URLs](images.md)
|
|
22
|
+
- [Backups](backups.md)
|
|
23
|
+
- [Redirects](redirects.md)
|
|
24
|
+
- [Schema & Social](schema.md)
|
|
25
|
+
- [Discovery](discovery.md): IndexNow, RSS feed, news sitemap, llms.txt
|
|
26
|
+
- [Robots.txt Rules](robots.md)
|
|
27
|
+
- [Search](search.md)
|
|
28
|
+
- [Link Manager](links.md)
|
|
29
|
+
- [Headings & TOC](headings.md)
|
|
30
|
+
- [Breadcrumb Nav](breadcrumbs.md)
|
|
31
|
+
- [Code Blocks](code-blocks.md)
|
|
32
|
+
- [File Downloads](files.md)
|
|
33
|
+
- [Private form uploads](form-uploads.md)
|
|
34
|
+
- [Videos](videos.md)
|
|
35
|
+
- [Reviews](reviews.md)
|
|
36
|
+
- [Custom Blocks](custom-blocks.md)
|
|
37
|
+
- [AI Enrichment](ai.md)
|
|
38
|
+
|
|
39
|
+
## Moving to EmDash
|
|
40
|
+
|
|
41
|
+
- [Migrating from WordPress](wordpress-import.md)
|
|
42
|
+
|
|
43
|
+
## Maintaining
|
|
44
|
+
|
|
45
|
+
- [Releasing](releasing.md): tags, GitHub releases and npm publishing
|
package/docs/ai.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# AI Enrichment
|
|
2
|
+
|
|
3
|
+
[Coywolf Pack documentation](README.md) · [Getting started](../README.md#getting-started)
|
|
4
|
+
|
|
5
|
+
Ported from Coywolf SEO's AI features, with the same prompts and validation:
|
|
6
|
+
|
|
7
|
+
- **Entities for schema** (`ai.entities`). The model lists the people, organizations, places, and things a page is about, without identifiers. Each name is looked up on Wikidata (`wbsearchentities`), and only items whose label or alias is that name (ignoring case, accents, and punctuation) count. When several match, the model may only pick one of them. Each chosen item's `instance of` (P31) claims are then checked (one Wikidata Query Service request per entry for all its items): disambiguation pages are dropped, a Person must be human, an Organization or Place can't be a creative work (the album instead of the band), and an Organization can't be a place. The JSON-LD `name` is Wikidata's label. The result is stored per entry as Schema.org `about` (main subjects) and `mentions`, each with `sameAs` links to Wikidata, Wikipedia, and the official website (P856) when there is one. Coywolf's Schema module reads them with `getEntryEntities(ctx | db, collection, id)` from `src/ai/entities.ts`.
|
|
8
|
+
- **Output entities on their own** (`ai.entitiesStandalone`). Adds the entities to the page's JSON-LD as a `WebPage` node (same `@id` as EmDash's `mainEntityOfPage`). Only for sites that don't use Coywolf Schema.
|
|
9
|
+
- **Meta descriptions** (`ai.descriptions`). Writes a description (under 155 characters, no clickbait) for published entries in SEO-enabled collections that don't have one. By default they're suggestions you edit and apply on the AI page; or have empty descriptions filled automatically. Either way it writes only EmDash's SEO panel field and never replaces a description someone wrote unless you choose **Replace**.
|
|
10
|
+
- **Image text** (`ai.imageText`). Writes alt text (and, if you turn it on, captions) for images on upload and in bulk, with accessibility-first prompts. Alt text and captions people wrote are kept unless you turn on overwrite. EmDash media has no title or description fields, so titles are shown on the AI page for reference only.
|
|
11
|
+
|
|
12
|
+
All four are off by default, under the **AI Enrichment** switch on the Coywolf Pack page.
|
|
13
|
+
|
|
14
|
+
## How it runs
|
|
15
|
+
|
|
16
|
+
Saving, publishing, or uploading never waits on AI: hooks only add the entry or image to a queue (after a short wait, so a burst of saves becomes one job, and only when the entry's text actually changed). A scheduled job works through the queue every two minutes (or with each cron tick, if the site's cron runs less often), a few items at a time (**Items per scheduled run**; raise it on a site with a slower cron), and stops for the day at **Max model calls per day** (default 200; an entry costs up to three calls, an image one). Each run also stays under 40 outbound requests (model calls plus Wikidata lookups), so it fits the Workers Free plan's subrequest limit; entries and images take turns, so a backlog of one doesn't hold up the other. Failed items are retried after 5 and 20 minutes, then marked as errors; a retry reuses the model output it already paid for. Deleting an entry removes its analysis. **Run bulk** on the AI page queues every published entry (skipping unchanged ones) or every image without alt text. The AI page also has a connection test, the queue status, suggestions to review, the analyzed entities (with **Export CSV**: one row per entity with its Wikidata, Wikipedia and website links, plus each entry's AI description), and a 30-day usage log with token counts.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
Pick a provider on **Plugins → AI Enrichment → Settings**:
|
|
21
|
+
|
|
22
|
+
- **Cloudflare Workers AI** (the default when the binding exists). Add the binding to `wrangler.jsonc`:
|
|
23
|
+
|
|
24
|
+
```jsonc
|
|
25
|
+
"ai": { "binding": "AI" }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The default model is `@cf/meta/llama-4-scout-17b-16e-instruct` for both text and images. A different binding name goes in `coywolfPlugin({ ai: { binding: "MY_AI" } })`.
|
|
29
|
+
- **Anthropic, OpenAI, or Google Gemini** with your own API key (stored encrypted, so the site needs `EMDASH_ENCRYPTION_KEY`). Defaults (checked October 2026): `claude-sonnet-5-5`, `gpt-6-luna`, `gemini-3.8-flash`. To use another model, such as a cheaper one or a newer one after a default is retired, enter its id in **Text model** and **Image model** on the Settings tab; leave them blank to follow the defaults.
|
|
30
|
+
|
|
31
|
+
Images are sent to the model at up to 3.5 MB. With a Cloudflare Images binding (`"images": { "binding": "IMAGES" }`, the same one EmDash uses for resizing) larger images are downscaled to 1568 pixels first; without one they're skipped. SVGs are skipped.
|
|
32
|
+
|
|
33
|
+
Set `ai: false` in `coywolfPlugin()` to leave the module out entirely. The module declares the `network:request` capability (for `api.anthropic.com`, `api.openai.com`, `generativelanguage.googleapis.com`, `www.wikidata.org`, and `query.wikidata.org`), plus content read/write (SEO panel only), schema read, and media read, bytes, and metadata write.
|
|
34
|
+
|
|
35
|
+
## Notes
|
|
36
|
+
|
|
37
|
+
- Only published entries are analyzed. Change the model or switch features and entries are re-analyzed the next time they're saved (or with **Re-analyze unchanged entries**).
|
|
38
|
+
- The daily limit is counted in UTC days. Each call's token usage is in the usage log; costs depend on your provider.
|
|
39
|
+
- Wikidata lookups are cached per Worker isolate and made one at a time, with `maxlag=5`. If Wikidata doesn't answer, the item is retried rather than saved unverified; if it asks to slow down (HTTP 429 or maxlag), every entity job waits for its `Retry-After` without counting as a failed attempt (after 10 waits in a row, the next counts as one). When an item lists several official websites, the preferred-rank one is kept, then https over http, then the first alphabetically.
|
package/docs/backups.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Backups
|
|
2
|
+
|
|
3
|
+
[Coywolf Pack documentation](README.md) · [Getting started](../README.md#getting-started)
|
|
4
|
+
|
|
5
|
+
- **Database**: a restorable SQL dump of the site's D1 database: users, passkeys, settings, redirects, menus, plugin data, and content. Search indexes rebuild automatically on restore.
|
|
6
|
+
- **Media**: a mirror of the R2 media bucket. Replaced or deleted files are kept under a dated folder until retention expires.
|
|
7
|
+
- **Private form uploads**: when the site has a `FORM_UPLOADS` binding (**Private form uploads**), a mirror of that bucket too, under `uploads/`. Set `backups.uploads` to another binding name, or `false` to leave uploads out. Keep the backup bucket private: these are files people sent through forms.
|
|
8
|
+
- **Large rows**: D1 rejects statements over 100 KB, but posts can be bigger. Rows that don't fit in one INSERT are written with their long values in pieces, and the restore puts them back together exactly. Plugin caches are left out (they rebuild on demand). Rows are read up to 500 at a time, fewer when they're large: each read is sized from the bytes the previous one returned (about 8 MB), and a read D1 refuses as too large (it rejects results over 50 MB from November 4, 2026) is asked again for fewer rows.
|
|
9
|
+
- **Admin**: **Back up now**, **Download** any backup (see below), and a dashboard widget that warns when backups stop. Settings (**Schedule and retention** on the Backups page): daily scheduled backup (default off), retention (default 30 days), staleness warning (default 36 hours).
|
|
10
|
+
- **Restore** (optional, see below): **Rewind to this backup** (D1 Time Travel, with **Undo rewind**), **Restore to a new database** (import plus per-table row-count check), and **Restore missing media** (media first, then private form uploads, a batch of 200 files per request until done).
|
|
11
|
+
|
|
12
|
+
Theme code isn't included: it lives in your Git repository.
|
|
13
|
+
|
|
14
|
+
## Download a backup
|
|
15
|
+
|
|
16
|
+
Each backup's menu has **Download (.sql.gz)**: the gzipped SQL dump of the whole database, saved as `<name>-<stamp>.sql.gz`. It restores into any D1 database (see **Manual restore**), so you always have your own copy. The page asks the `backups/download-link` route (admins only) for a link that works for ten minutes, and the `coywolfPack()` middleware streams the file from R2 at `/_coywolf-pack/backup/<token>/<file name>`, so dumps of any size download without the Worker holding them in memory. Links are random, unlisted tokens stored in the backup bucket (`downloads/`), sent with `Cache-Control: private, no-store`; expired ones are deleted when the next link is made. Media isn't part of the download: the media mirror stays in the backup bucket (copy it with `rclone` or the R2 dashboard).
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
1. Create a backup bucket and bind it as `BACKUPS` in `wrangler.jsonc`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx wrangler r2 bucket create coywolf-backups
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```jsonc
|
|
27
|
+
"r2_buckets": [
|
|
28
|
+
{ "binding": "MEDIA", "bucket_name": "coywolf-media" },
|
|
29
|
+
{ "binding": "BACKUPS", "bucket_name": "coywolf-backups" }
|
|
30
|
+
]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Options: `database` (default `"DB"`), `media` (`"MEDIA"`), `backups` (`"BACKUPS"`), `name` (dump file prefix, default `"database"`).
|
|
34
|
+
3. Downloads need the `coywolfPack()` middleware (see the Redirects setup).
|
|
35
|
+
|
|
36
|
+
## Enable restore
|
|
37
|
+
|
|
38
|
+
Restores use the Cloudflare API, because a Worker can't create databases or time-travel through its D1 binding.
|
|
39
|
+
|
|
40
|
+
1. Create an API token with one permission, **Account → D1 → Edit**.
|
|
41
|
+
2. Store it as a Worker secret. It isn't a plugin setting, because a rewind rolls plugin settings back too:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx wrangler secret put BACKUPS_API_TOKEN
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
3. Add the IDs and deploy:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
coywolfPlugin({
|
|
51
|
+
backups: { name: "coywolf", restore: { accountId: "<account id>", databaseId: "<D1 database id>" } },
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Rewinds save an undo point to the backup bucket first; undo steps back one rewind at a time.
|
|
56
|
+
|
|
57
|
+
## Backup layout
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
d1/<stamp>/<name>.sql.gz database dump
|
|
61
|
+
d1/<stamp>/manifest.json stamp, size, SHA-256, source, row counts, Time Travel bookmark
|
|
62
|
+
media/current/<key> mirror of the media bucket
|
|
63
|
+
media/changed/<stamp>/<key> media replaced or deleted at that backup
|
|
64
|
+
uploads/current/<key> mirror of the private form uploads bucket
|
|
65
|
+
uploads/changed/<stamp>/<key> uploads replaced or deleted at that backup
|
|
66
|
+
restore/undo/<time>.json undo points for rewinds
|
|
67
|
+
downloads/<token>.json download links (ten minutes each)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
External jobs (a nightly GitHub Action, for example) can write to the same layout, and the admin lists those backups too.
|
|
71
|
+
|
|
72
|
+
## Manual restore
|
|
73
|
+
|
|
74
|
+
Restore a downloaded backup (or one copied from the bucket) into a **new, empty** D1 database and switch the `DB` binding to it. Never import over the live database:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx wrangler d1 create coywolf-restore
|
|
78
|
+
gunzip -c coywolf.sql.gz > restore.sql
|
|
79
|
+
npx wrangler d1 execute coywolf-restore --remote --file restore.sql
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Keep `EMDASH_ENCRYPTION_KEY` in a password manager. It isn't in backups, and encrypted plugin settings can't be read without it.
|
|
83
|
+
|
|
84
|
+
## Notes
|
|
85
|
+
|
|
86
|
+
- `wrangler d1 export` refuses databases with FTS5 tables (EmDash search). The dump writes tables, then search tables and their triggers, then rows with parent tables before child tables (D1 imports large files in batches, so deferred foreign keys aren't enough), then indexes and the remaining triggers.
|
|
87
|
+
- Backups run in one Worker request, so they suit small and medium sites. The dump is streamed: rows are read 500 at a time (paged by rowid) and gzipped straight into the backup bucket (as a multipart upload in 5 MB parts once it's bigger than one part), so the Worker never holds the whole dump in memory. The database is saved before the media mirror runs. The mirror copies at most 300 changed files per run, 8 at a time; the rest follow on the next run.
|
|
88
|
+
- Integers larger than 2^53 lose precision (D1 returns JavaScript numbers). EmDash stores IDs as text.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Breadcrumb Nav
|
|
2
|
+
|
|
3
|
+
[Coywolf Pack documentation](README.md) · [Getting started](../README.md#getting-started)
|
|
4
|
+
|
|
5
|
+
An accessible breadcrumb trail (`<nav aria-label="Breadcrumb">` around an `<ol>`, `aria-current="page"` on the current page, separators in CSS that screen readers skip), as a component for theme layouts and as a **Breadcrumbs** block for content. Off until you turn it on under **Plugins → Coywolf Pack**:
|
|
6
|
+
|
|
7
|
+
| Feature | Default | What it does |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `breadcrumbs` | off | Main switch: the `Breadcrumbs` component, the **Breadcrumbs** block, and the **Breadcrumb Nav** page |
|
|
10
|
+
|
|
11
|
+
Until 0.6.0 this was the `headings.breadcrumbs` sub-feature of Headings & TOC. Sites keep their state: while `breadcrumbs` has never been saved, it follows the stored `headings.breadcrumbs` switch (on only if Headings & TOC was on too), and the settings fall back to the breadcrumb defaults stored with Headings & TOC until the Breadcrumb Nav page saves its own. The block type (`coywolf-breadcrumbs`), the `Breadcrumbs` export and the stored block data are unchanged, and Breadcrumb Nav no longer depends on Headings & TOC being on.
|
|
12
|
+
|
|
13
|
+
Site defaults live on **Breadcrumb Nav** (with a live preview): separator (`/`, `›`, `»`, `•`, `→`, `>`, or a custom string of up to 8 characters), home label, and whether the trail starts with the home page and ends with the current page. The page also has a step-by-step guide for adding the component to a theme, with copyable code.
|
|
14
|
+
|
|
15
|
+
## Setup
|
|
16
|
+
|
|
17
|
+
Add the component to a layout, passing the same page context you give `EmDashHead`:
|
|
18
|
+
|
|
19
|
+
```astro
|
|
20
|
+
---
|
|
21
|
+
import { EmDashHead } from "emdash/ui";
|
|
22
|
+
import { createPublicPageContext } from "emdash/page";
|
|
23
|
+
import { Breadcrumbs } from "@coywolf/emdash/astro";
|
|
24
|
+
|
|
25
|
+
const page = createPublicPageContext({ Astro, kind: "custom", title, pageTitle: title, breadcrumbs });
|
|
26
|
+
---
|
|
27
|
+
<EmDashHead page={page} />
|
|
28
|
+
…
|
|
29
|
+
<Breadcrumbs page={page} />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Give each page its real trail with `breadcrumbs` (root first, current page last); `[]` hides the trail on that page, and leaving it out derives one from the URL:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
breadcrumbs: [
|
|
36
|
+
{ name: "Home", url: "/" },
|
|
37
|
+
{ name: category.label, url: `/${category.slug}/` },
|
|
38
|
+
{ name: entry.data.title, url: Astro.url.pathname },
|
|
39
|
+
],
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Props: `page` (the `PublicPageContext` you pass to `EmDashHead`), `items` (`{ name, url }[]`), `title`, `separator` (`slash`, `chevron`, `guillemet`, `bullet`, `arrow`, `gt`, or any short string), `homeLabel`, `showHome`, `showCurrent`, `class`, `label` (the nav's `aria-label`, default "Breadcrumb"). Props override the site defaults.
|
|
43
|
+
|
|
44
|
+
The trail comes from, in order: `items`, `page.breadcrumbs`, the trail the theme gave `EmDashHead` for the same URL (picked up by the module's `page:metadata` hook, so the Breadcrumbs block uses it too), or the URL path (home, one crumb per path segment with a readable name, then the page title). Give themes with archives or nested content a real trail through `page.breadcrumbs`: derived ancestor links point at whatever the path segments are, which may not be pages.
|
|
45
|
+
|
|
46
|
+
With Schema & Social's `schema.breadcrumbs` on, the `BreadcrumbList` is built from the same `page.breadcrumbs`, so one trail feeds both the visible nav and the structured data. The schema uses the trail as given (include Home and the current page); the nav applies the start-with-home and end-with-current settings.
|
|
47
|
+
|
|
48
|
+
Without theme changes, add a **Breadcrumbs** block to an entry instead. Each block can override the separator, home label, and whether the current page shows; the entry's title is saved with the block for the last crumb.
|
|
49
|
+
|
|
50
|
+
## Styling
|
|
51
|
+
|
|
52
|
+
Classes: `.cw-breadcrumbs` (the `<nav>`, plus `.cw-breadcrumbs--sep-<preset>`), `.cw-breadcrumbs__list` (the `<ol>`, a wrapping flex row), `.cw-breadcrumbs__item` (each `<li>`; the separator is its `::before`), `.cw-breadcrumbs__current` (the current page). The separator is the `--cw-bc-sep` custom property (a CSS string). Links use the theme's colors. The component's rules are single-class, so a selector like `.site-header .cw-breadcrumbs a { … }` overrides them regardless of stylesheet order.
|
|
53
|
+
|
|
54
|
+
## Notes
|
|
55
|
+
|
|
56
|
+
- Server-rendered with no script; the small global stylesheet (`cw-` classes only) is bundled on pages that import the component, even with the feature off.
|
|
57
|
+
- A trail of one crumb (just Home, or just the page) isn't shown.
|
|
58
|
+
- The Breadcrumbs block picks up the theme's trail only when the theme renders `<EmDashHead page={page} />` with the page's real `url`; it's matched by path and query string, so locales and variants don't mix.
|
|
59
|
+
- The module's `content:beforeSave` hook only writes `_title` on Breadcrumbs blocks (it declares the `content:write` capability EmDash requires for the hook).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Code Blocks
|
|
2
|
+
|
|
3
|
+
[Coywolf Pack documentation](README.md) · [Getting started](../README.md#getting-started)
|
|
4
|
+
|
|
5
|
+
A port of Coywolf Code Block Enhancer. It replaces the site renderer for EmDash's built-in code block (the editor's language picker is unchanged) with one that highlights code **on the server** using highlight.js grammars (via lowlight, which EmDash already ships). Visitors download no highlighting script.
|
|
6
|
+
|
|
7
|
+
- **Themes** (**Plugins → Code Blocks**, with a live preview): Coywolf Auto (light/dark by system), Coywolf Always light, Coywolf Always dark, four light/dark pairs that follow the system setting (GitHub, Atom One, A11y, Tokyo Night), and 19 popular highlight.js themes (GitHub, GitHub Dark, Monokai, Nord, Dracula, Solarized, Visual Studio, Xcode, Night Owl and more). The theme is the `codeBlocksTheme` setting. Every theme meets WCAG AA contrast (4.5:1): highlight.js colors below that against their theme's background (comments in most themes, Rosé Pine's body text) are darkened or lightened, keeping their hue, and line numbers and the language label use muted colors that still pass (the faded text they always had, raised only where that fell under 4.5:1).
|
|
8
|
+
- **Language label**, **Copy button** and **Line numbers** are sub-features. The copy button follows the WordPress plugin's accessible pattern: a labeled button, a polite live region that announces "Copied to clipboard", a two-second confirmation, and no animation for visitors who prefer reduced motion. Line numbers are drawn with CSS, so they're never selected or copied. The header shows the language on the left (file names aren't shown) with the copy button on the right.
|
|
9
|
+
- **Page weight**: the layout CSS (about 2 KB), the active theme's CSS (1–4 KB) and, with the copy button on, one small inline script (under 1 KB) are inlined once per page by the first code block. Pages without code blocks get nothing, and no external requests are made.
|
|
10
|
+
|
|
11
|
+
Feature switches: `codeBlocks` (main), `codeBlocks.label`, `codeBlocks.copy`, `codeBlocks.lineNumbers`, all off by default. While `codeBlocks` is off, code blocks render exactly as EmDash's built-in renderer does.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
Nothing beyond the plugin itself: the block renderer is registered through the plugin's `componentsEntry`, so it applies wherever the site renders Portable Text with EmDash's `<PortableText>`. It reads the theme and switches from the site's D1 database (`DB`), cached for five minutes per Worker isolate (a save on the Code Blocks page applies at once in that isolate; see [Database reads](internals.md)).
|
|
16
|
+
|
|
17
|
+
## Notes
|
|
18
|
+
|
|
19
|
+
- Languages: Prism names used by Code Block Enhancer for WordPress work too (`markup` is HTML, `svg` and `mathml` are XML). EmDash's editor list (Astro, Svelte and Vue are highlighted as HTML, MDX as Markdown, TOML as INI) plus highlight.js's common grammars. Unknown languages, blocks over 30,000 characters and blocks with any line over 2,000 characters are shown as plain text (highlight.js slows sharply on long lines). Rendered blocks are cached in memory (200 per Worker isolate), so repeat page views don't re-highlight.
|
|
20
|
+
- Theme CSS is generated from highlight.js's own stylesheets (BSD-3-Clause, each theme's original credits kept) by `node scripts/gen-code-themes.mjs`.
|
|
21
|
+
- Tests: `node --import ./test/ts-resolve.mjs --test src/codeBlocks/render.test.ts`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Content URLs
|
|
2
|
+
|
|
3
|
+
[Coywolf Pack documentation](README.md) · [Getting started](../README.md#getting-started)
|
|
4
|
+
|
|
5
|
+
Several modules link to entries: llms.txt and the news sitemap, IndexNow, per-entry Markdown, the video sitemap, Schema previews, search results, and removed-content redirects. By default they resolve an entry's URL the way EmDash does, from the collection's URL pattern (`{slug}`, `{id}`, and date tokens such as `{year}`). If your theme routes a collection differently, tell the pack with `urls`:
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
// coywolf.com serves posts under their category path, as WordPress did (/news/seo/<slug>/)
|
|
9
|
+
coywolfPlugin({
|
|
10
|
+
urls: { posts: "/{termpath:category|uncategorized}/{slug}/" },
|
|
11
|
+
});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Patterns take EmDash's tokens plus taxonomy tokens:
|
|
15
|
+
|
|
16
|
+
- `{term:<taxonomy>}` is the slug of the entry's first term in that taxonomy, in the order EmDash's `getTermsForEntries` returns them (by label).
|
|
17
|
+
- `{term:<taxonomy>|<fallback>}` uses `<fallback>` when the entry has no term. Without a fallback, an entry with no term has no URL.
|
|
18
|
+
- `{category}` is shorthand for `{term:category|uncategorized}`.
|
|
19
|
+
- `{termpath:<taxonomy>}` (and `{termpath:<taxonomy>|<fallback>}`) is the same term with its parent terms in front, root first, like WordPress's `%category%` permalink: `news/seo` for SEO under News, `guides/method-seo/structure` three levels down. A term with no parent is just its slug. Parents come from EmDash's categories (the parent set under **Taxonomies**), read once per request or build. Chains stop at a loop or after 32 levels.
|
|
20
|
+
- `{pagepath}` is the entry's slug with its parent pages' slugs in front (`apps/coywolf-seo`). EmDash entries have no parent field, so the parents come from the `pageParents` option (slug → parent slug). An entry with no parent is just its slug.
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
// coywolf.com: WordPress's /%category%/%postname%/ with the full category path, and nested pages
|
|
24
|
+
coywolfPlugin({
|
|
25
|
+
urls: {
|
|
26
|
+
posts: "/{termpath:category|uncategorized}/{slug}/",
|
|
27
|
+
pages: "/{pagepath}/",
|
|
28
|
+
},
|
|
29
|
+
pageParents: { "coywolf-seo": "apps", "coywolf-files": "apps" },
|
|
30
|
+
// Optional: parents for categories that have none in EmDash yet (e.g. right after a WordPress import).
|
|
31
|
+
termParents: { category: { seo: "news", "method-seo": "guides", structure: "method-seo" } },
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`termParents` (taxonomy → term slug → parent slug) is a fallback: it's used only for terms that have no parent in EmDash, so once the parents are restored (see [Migrating from WordPress](wordpress-import.md), "Category and page parents") it can go.
|
|
36
|
+
|
|
37
|
+
Collections without an override keep EmDash's resolution. The pack also maps paths back to entries (for example, the Markdown source at `/news/seo/some-post/index.html.md` resolves only when `news/seo` is the post's primary category path), so a wrong category doesn't match. With `{termpath:…}` the whole path must match: `/news/seo/some-post/` resolves, `/seo/some-post/` doesn't (the theme should redirect it, as WordPress did). With `{pagepath}`, a page under a parent resolves only at its full path.
|
|
38
|
+
|
|
39
|
+
Trailing slashes follow Astro's `trailingSlash` setting. With the default (`"ignore"`), an override keeps the pattern's own trailing slash. Set `trailingSlash: "always" | "never"` on `coywolfPlugin()` to force one for every entry URL the pack builds. Override URLs get no locale prefix.
|
|
40
|
+
|
|
41
|
+
The options are read when the Worker starts (EmDash creates the plugin then), so the middleware and Astro components use them too. Sites can resolve URLs the same way with `entryUrl`, `entryUrls`, and `matchEntryPath` from `@coywolf/emdash/astro`.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Custom Blocks
|
|
2
|
+
|
|
3
|
+
[Coywolf Pack documentation](README.md) · [Getting started](../README.md#getting-started)
|
|
4
|
+
|
|
5
|
+
Six editor blocks (slash menu → Content), each with its own switch, named after Coywolf's WordPress plugin, Custom Blocks. They're server-rendered plain HTML with no script, accessible (landmarks with names, native disclosure widgets, real quotations), and share one small stylesheet (under 5 KB) inlined once per page before the first block. They're rendered by the pack's components rather than stored as HTML, so EmDash's HTML sanitizer doesn't strip `<details>`, `<summary>`, `<aside>` or `<figure>`.
|
|
6
|
+
|
|
7
|
+
| Feature | Default | Block | Fields |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| `customBlocks` | off | (module switch; the Custom Blocks page) | |
|
|
10
|
+
| `customBlocks.note` | off | **Note**: a callout in an `<aside>` labeled by its title | Kind (Note, Editor's note, Tip, Warning), title (empty = the kind's name), hide title, title style (bold text or an H2–H4), text |
|
|
11
|
+
| `customBlocks.details` | off | **Details**: native `<details>`/`<summary>` | Style (Details, or Transcript: the hidden text sits in a tinted panel), summary (default "Details" / "Read the transcript"), hidden text, open when the page loads |
|
|
12
|
+
| `customBlocks.disclosure` | off | **Affiliate disclosure**: small, muted text in an `<aside>` labeled "Affiliate disclosure" | Disclosure (Affiliate links or Amazon Associates), wording for this page (optional; empty uses the site's) |
|
|
13
|
+
| `customBlocks.quote` | off | **Quote**: `<figure><blockquote cite>…</blockquote><figcaption><cite>…</cite></figcaption></figure>` | Quote, who said it, source URL (the `cite` attribute) |
|
|
14
|
+
| `customBlocks.testimonial` | off | **Testimonial**: `<figure><blockquote>…</blockquote><figcaption>` photo, name, title `</figcaption></figure>`; the quote sits in a speech bubble with the person below it | What they said, name, title, photo (media library), link for the name (e.g. a social profile), link for the title (e.g. their company) |
|
|
15
|
+
| `customBlocks.podcast` | off | **Podcast links**: a `<section>` labeled by its heading, with a list of links named by service (Apple Podcasts, Spotify, YouTube, Amazon Music, Overcast, Pocket Casts, RSS feed), each with a small line icon | Links (the site's podcast from the Custom Blocks page, or only this block's), heading (empty = the site's), a link per service |
|
|
16
|
+
|
|
17
|
+
**Text fields** are plain text: a blank line starts a new paragraph, a single line break is a line break, `[link text](https://…)` is a link and `**text**` is bold. Simple HTML works too, so imported WordPress content keeps its exact markup: links (`href`, `title`, `rel` limited to `nofollow`, `sponsored`, `ugc` and the like), bold, italics, `<code>`, `<abbr title>`, `<q cite>`, `<mark>`, `<sub>`/`<sup>`, lists, paragraphs, H2–H6, `<figure>` and `<img>`, and text styling in `style` (color, font size and weight, …). Everything else is dropped (its text stays), links are limited to http(s), mailto, tel and site-relative URLs, and tags left open are closed, so a block can't break the page around it. Titles, summaries and citations are one line (no paragraphs or block tags). A testimonial's name and title are plain text.
|
|
18
|
+
|
|
19
|
+
**Testimonials** have no schema: Google doesn't show review rich results for reviews a site collects and shows about itself, and a testimonial has no rating. The photo's alt text is empty because the name right next to it says who it is.
|
|
20
|
+
|
|
21
|
+
**Custom Blocks page** (**Plugins → Coywolf Pack → Custom Blocks**): the site's disclosure wording for affiliate links and for Amazon Associates (Amazon requires "As an Amazon Associate I earn from qualifying purchases."), an optional disclosure page linked after it ("Learn more" by default); the podcast's links (one per service; empty ones are left out), heading ("Subscribe to the podcast" by default), heading style (H2–H4 or bold text) and whether to show icons; and live previews of every block. A Podcast links block with no links shows nothing.
|
|
22
|
+
|
|
23
|
+
**Styling**: colors are mixed from the theme's text color (`currentColor`), so the blocks follow light and dark themes; notes add a hue per kind, a little stronger in dark mode. Override with the theme's CSS: `--cw-note-accent` on `.cw-note` (or `.cw-note--tip`, …), and the classes `.cw-note`, `__title`, `__body`; `.cw-details`, `--transcript`, `__summary`, `__body`; `.cw-disclosure`, `__text`, `__link`; `.cw-quote`, `__text`, `__caption`; `.cw-testimonial`, `__quote`, `__person`, `__photo`, `__name`, `__title`; `.cw-podcast`, `__title`, `__links`, `__link` (and `--apple`, `--spotify`, … per service), `__icon`.
|
|
24
|
+
|
|
25
|
+
**Renamed in 0.12.0**: this module was called Content Blocks. Its page moved from `/content-blocks` to `/custom-blocks` (the old address still opens it), and its feature ids from `contentBlocks*` to `customBlocks*`; saved switches carry over, and the block types (`coywolf-note`, `coywolf-details`, `coywolf-disclosure`, `coywolf-quote`) and the saved disclosure wording are unchanged.
|
|
26
|
+
|
|
27
|
+
`customBlocks: false` leaves the module out (`contentBlocks: false` still works).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Discovery
|
|
2
|
+
|
|
3
|
+
[Coywolf Pack documentation](README.md) · [Getting started](../README.md#getting-started)
|
|
4
|
+
|
|
5
|
+
Help search engines, feed readers and AI agents find your content. Ported from Coywolf SEO for WordPress (the RSS feed is new). Everything is off until you turn it on under **Plugins → Coywolf Pack**: the **Discovery** switch, then any of its four parts (here or with the switch next to each part's title on the Discovery page). Settings and status are under **Plugins → Coywolf Pack → Discovery**.
|
|
6
|
+
|
|
7
|
+
| Feature switch | Default | What it does |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `discovery` | off | Main switch for the module |
|
|
10
|
+
| `discovery.indexnow` | off | IndexNow submissions and the key file |
|
|
11
|
+
| `discovery.newsSitemap` | off | `/news-sitemap.xml` |
|
|
12
|
+
| `discovery.feed` | off | RSS feed at `/feed/`, category feeds, and the `<link rel="alternate" type="application/rss+xml">` head tag |
|
|
13
|
+
| `discovery.llms` | off | `/llms.txt`, Markdown versions of entries, and the `<link rel="alternate" type="text/markdown">` head tag |
|
|
14
|
+
|
|
15
|
+
- **IndexNow**: when an entry is published, updated (in collections without draft revisions), unpublished, or deleted, its public URL goes to `api.indexnow.org` (or `www.bing.com`), which shares it with Bing, Yandex, Seznam, Naver, and the other participating engines. Changes made within 3 seconds of each other go out as one request, after the response is sent. A changed slug submits the old URL too. Entries set to noindex aren't submitted. The key is generated the first time it's needed (or when you open the Discovery page with IndexNow on) and served at `/<key>.txt`; **New key** replaces it. The page shows the last 25 submissions and has **Submit home page** for checking the setup. While the site is in EmDash's staging mode (**Settings → General → Site Status**, EmDash 1.5+), hidden from search engines, nothing is submitted, **Submit home page** is refused, and the Discovery page says so; submissions start when the site goes live.
|
|
16
|
+
- **News sitemap**: entries published in the last 48 hours (the window Google News uses), newest first, at most 1,000, with publication name and language (defaults: site title and locale). Choose the collections (default: `posts`). Also served at `/coywolf-news-sitemap.xml`, Coywolf SEO's URL, so a sitemap submitted from WordPress keeps working. Submit it in Google Search Console; to list it in robots.txt, add `Sitemap: https://coywolf.com/news-sitemap.xml` to the custom robots.txt under **Settings → SEO**. The module doesn't change robots.txt.
|
|
17
|
+
- **RSS feed**: RSS 2.0 at `/feed/` (WordPress's feed URL) with the newest entries of the chosen collections (default `posts`, 20 entries, up to 100), each with its title, link, author (the first byline), publish date, category (its first term in the taxonomy its URL uses, or `category`), and summary (the `excerpt` field or SEO description). The channel has the site title, tagline, locale, and the site logo (or favicon) when it's a PNG, JPEG or GIF. Every page gets `<link rel="alternate" type="application/rss+xml" href="https://coywolf.com/feed/">` in the head, so feed readers, apps and bots find it. **Category feeds** (on by default): when a collection's URL override is a term then the slug (`urls: { posts: "/{termpath:category|uncategorized}/{slug}/" }`, or `{term:…}` or `{category}`, with an optional fixed prefix), each term's archive URL + `feed/` is that term's feed, including its child terms' entries (`/news/feed/`, `/news/seo/feed/`), titled "<term> Archives - <site>" as WordPress titles them. A path that isn't a feed (no such term) goes on to the site. Entries set to noindex aren't listed. Feeds are built when requested and kept in the edge cache (Workers Cache, with Astro route caching on) for up to an hour, tagged with their collections and taxonomy: EmDash clears those tags on every publish, live edit, removal and term change, and saving the Discovery settings clears all feeds, so a change shows up on the next request. Readers may keep their own copy for up to 15 minutes (`Cache-Control: public, max-age=900`). Paths that aren't feeds are remembered in each isolate's memory for a minute. `/feed` without the slash is left to the site (which redirects it to `/feed/`).
|
|
18
|
+
- **llms.txt** ([llmstxt.org](https://llmstxt.org/)): the site name, a summary (default: the site tagline), an optional introduction, then one list per collection (newest first, 100 per collection) and an **Optional** list for the rest, up to 500 entries (configurable up to 5,000). Each link points at the entry's Markdown version, or at the page itself when Markdown is off. Excerpts (`excerpt` field or SEO description) become link notes. A static `public/llms.txt` takes precedence, because Cloudflare serves static assets before the Worker runs.
|
|
19
|
+
- **Markdown versions**: each entry at its URL + `index.html.md` (`/blog/post/` → `/blog/post/index.html.md`, Coywolf SEO's convention). The Portable Text body becomes Markdown (headings, paragraphs, lists, links, images with captions, code, block quotes, tables, and horizontal rules; other blocks are skipped) under YAML frontmatter with the title, URL, dates, sources, and an optional license. There's no `Accept: text/markdown` negotiation on the entry's own URL: Cloudflare's cache key ignores `Accept`, so one such request could put Markdown into the cached HTML page. Responses carry `X-Markdown-Tokens`, an estimate at about 4 characters per token. Entry pages get `<link rel="alternate" type="text/markdown">` in the head.
|
|
20
|
+
|
|
21
|
+
Only published, non-noindex entries in routable collections are included. URLs come from EmDash's own routing (each collection's URL pattern, date tokens, locale prefix, and trailing-slash setting), so they match the native sitemap.
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
Nothing to configure beyond the feature switches. The module's site URLs are served by the pack middleware (see Redirects → Setup), so add `coywolfPack()` from `@coywolf/emdash/middleware` to `src/middleware.ts` if you haven't. The plugin asks for `content:read`, `schema:read`, `bylines:read` (feed authors), `network:request` (hosts `api.indexnow.org` and `www.bing.com`), and `hooks.page-fragments:register`. A theme that has its own feed route or head link can drop them: the pack's middleware answers `/feed/` first, and the head link would be listed twice.
|
|
26
|
+
|
|
27
|
+
## Caching and limits
|
|
28
|
+
|
|
29
|
+
- llms.txt and the news sitemap are stored in plugin storage and visitors are always served the stored copy. Content changes (publish, unpublish, delete, or a live edit) and settings saves rebuild them in the background after the response, debounced by 5 seconds; a copy older than its limit (5 minutes for the news sitemap, whose window moves; 24 hours for llms.txt) is served once more while a background rebuild runs. Only the very first request, before anything is stored, builds inline. **Rebuild now** on the Discovery page rebuilds both immediately. Each Worker isolate also keeps responses for a minute (up to about 2 MB), and CDNs may keep `llms.txt` and `.md` files for an hour (`Cache-Control: public, max-age=3600`; 5 minutes for the news sitemap).
|
|
30
|
+
- Markdown URLs resolve through EmDash's routing (`resolveEmDashPath`, or `/{collection}/{slug}` for collections without a URL pattern), then are checked against the entry's public URL. Entries behind a locale prefix or a custom route don't get a Markdown version.
|
|
31
|
+
- An entry's old URL is known only if it was published while IndexNow was on, so unpublishing or deleting an entry published earlier submits nothing.
|
|
32
|
+
- The news sitemap has no category include/exclude filter (Coywolf SEO's WordPress option); pick collections instead. Coywolf SEO's AI entity sections (a Labs feature) aren't ported.
|