@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.
Files changed (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +203 -2
  3. package/docs/README.md +45 -0
  4. package/docs/ai.md +39 -0
  5. package/docs/backups.md +88 -0
  6. package/docs/breadcrumbs.md +59 -0
  7. package/docs/code-blocks.md +21 -0
  8. package/docs/content-urls.md +41 -0
  9. package/docs/custom-blocks.md +27 -0
  10. package/docs/discovery.md +32 -0
  11. package/docs/files.md +44 -0
  12. package/docs/form-uploads.md +59 -0
  13. package/docs/headings.md +37 -0
  14. package/docs/images.md +141 -0
  15. package/docs/internals.md +13 -0
  16. package/docs/links.md +34 -0
  17. package/docs/performance.md +72 -0
  18. package/docs/redirects.md +84 -0
  19. package/docs/releasing.md +21 -0
  20. package/docs/reviews.md +89 -0
  21. package/docs/robots.md +81 -0
  22. package/docs/schema.md +79 -0
  23. package/docs/search.md +114 -0
  24. package/docs/videos.md +48 -0
  25. package/docs/wordpress-import.md +198 -0
  26. package/package.json +79 -5
  27. package/src/admin/admin-css.ts +57 -0
  28. package/src/admin/ai.tsx +915 -0
  29. package/src/admin/backups.tsx +613 -0
  30. package/src/admin/breadcrumbs.tsx +542 -0
  31. package/src/admin/codeBlocks.tsx +204 -0
  32. package/src/admin/customBlocks.tsx +321 -0
  33. package/src/admin/dirty.ts +20 -0
  34. package/src/admin/discovery.tsx +463 -0
  35. package/src/admin/download.ts +28 -0
  36. package/src/admin/feature-switches.tsx +124 -0
  37. package/src/admin/features.tsx +142 -0
  38. package/src/admin/files.tsx +791 -0
  39. package/src/admin/formUploads.tsx +445 -0
  40. package/src/admin/guides.tsx +18 -0
  41. package/src/admin/headings.tsx +262 -0
  42. package/src/admin/images.tsx +825 -0
  43. package/src/admin/index.tsx +62 -0
  44. package/src/admin/links.tsx +980 -0
  45. package/src/admin/performance.tsx +330 -0
  46. package/src/admin/preview.tsx +63 -0
  47. package/src/admin/redirects-removed.tsx +266 -0
  48. package/src/admin/redirects.tsx +470 -0
  49. package/src/admin/reviews.tsx +232 -0
  50. package/src/admin/robots-bots.tsx +647 -0
  51. package/src/admin/robots-shared.tsx +109 -0
  52. package/src/admin/robots-wizard.tsx +748 -0
  53. package/src/admin/robots.tsx +911 -0
  54. package/src/admin/save-bar.tsx +134 -0
  55. package/src/admin/schema.tsx +1187 -0
  56. package/src/admin/search.tsx +355 -0
  57. package/src/admin/settings-ui.tsx +87 -0
  58. package/src/admin/trash-prompt-core.ts +135 -0
  59. package/src/admin/trash-prompt.tsx +156 -0
  60. package/src/admin/usage.tsx +301 -0
  61. package/src/admin/videos-display.tsx +248 -0
  62. package/src/admin/videos.tsx +1202 -0
  63. package/src/admin/wpImport.tsx +1516 -0
  64. package/src/ai/entities.ts +59 -0
  65. package/src/ai/export.ts +61 -0
  66. package/src/ai/logic.ts +606 -0
  67. package/src/ai/pack.ts +121 -0
  68. package/src/ai/prompts.ts +87 -0
  69. package/src/ai/providers.ts +276 -0
  70. package/src/ai/routes.ts +359 -0
  71. package/src/ai/store.ts +301 -0
  72. package/src/ai/wikidata.ts +87 -0
  73. package/src/ai/worker.ts +530 -0
  74. package/src/astro/astro-modules.d.ts +6 -0
  75. package/src/astro/breadcrumbs/Breadcrumbs.astro +129 -0
  76. package/src/astro/breadcrumbs/BreadcrumbsBlock.astro +12 -0
  77. package/src/astro/codeBlocks/CodeBlock.astro +62 -0
  78. package/src/astro/codeBlocks/EmDashCode.astro +61 -0
  79. package/src/astro/customBlocks/CustomBlock.astro +64 -0
  80. package/src/astro/customBlocks/Details.astro +8 -0
  81. package/src/astro/customBlocks/Disclosure.astro +8 -0
  82. package/src/astro/customBlocks/Note.astro +8 -0
  83. package/src/astro/customBlocks/Podcast.astro +8 -0
  84. package/src/astro/customBlocks/Quote.astro +8 -0
  85. package/src/astro/customBlocks/Testimonial.astro +8 -0
  86. package/src/astro/files/FileCard.astro +103 -0
  87. package/src/astro/headings/HeadingBlock.astro +129 -0
  88. package/src/astro/headings/TableOfContents.astro +45 -0
  89. package/src/astro/headings/request.ts +19 -0
  90. package/src/astro/images/ResponsiveImage.astro +60 -0
  91. package/src/astro/index.ts +84 -0
  92. package/src/astro/reviews/CoywolfReview.astro +27 -0
  93. package/src/astro/reviews/Review.astro +49 -0
  94. package/src/astro/search/SearchBox.astro +61 -0
  95. package/src/astro/search/SearchBoxForm.astro +509 -0
  96. package/src/astro/tables/CaptionedTable.astro +72 -0
  97. package/src/astro/videos/CoywolfVideo.astro +448 -0
  98. package/src/backups/download.ts +112 -0
  99. package/src/backups/dump.ts +288 -0
  100. package/src/backups/module.ts +276 -0
  101. package/src/backups/pack.ts +33 -0
  102. package/src/backups/restore.ts +209 -0
  103. package/src/backups/store.ts +409 -0
  104. package/src/breadcrumbs/pack.ts +113 -0
  105. package/src/breadcrumbs/settings.ts +102 -0
  106. package/src/breadcrumbs/stamp.ts +40 -0
  107. package/src/breadcrumbs/trail.ts +123 -0
  108. package/src/codeBlocks/contrast.ts +179 -0
  109. package/src/codeBlocks/hljs.d.ts +5 -0
  110. package/src/codeBlocks/pack.ts +110 -0
  111. package/src/codeBlocks/render.ts +304 -0
  112. package/src/codeBlocks/settings.ts +18 -0
  113. package/src/codeBlocks/themes.generated.ts +304 -0
  114. package/src/codeBlocks/themes.ts +114 -0
  115. package/src/core/compose.ts +244 -0
  116. package/src/core/content-url.ts +888 -0
  117. package/src/core/csv.ts +119 -0
  118. package/src/core/d1-batch.ts +65 -0
  119. package/src/core/entry-docs.ts +175 -0
  120. package/src/core/features-module.ts +48 -0
  121. package/src/core/features.ts +392 -0
  122. package/src/core/generation.ts +88 -0
  123. package/src/core/guide-content.ts +166 -0
  124. package/src/core/module.ts +104 -0
  125. package/src/core/read-failures.ts +57 -0
  126. package/src/core/request-memo.ts +71 -0
  127. package/src/core/secrets.ts +83 -0
  128. package/src/core/settings-enhance.ts +74 -0
  129. package/src/core/site.ts +36 -0
  130. package/src/core/storage.ts +27 -0
  131. package/src/customBlocks/pack.ts +260 -0
  132. package/src/customBlocks/render.ts +424 -0
  133. package/src/customBlocks/rich.ts +313 -0
  134. package/src/customBlocks/settings.ts +46 -0
  135. package/src/discovery/build.ts +252 -0
  136. package/src/discovery/feed-build.ts +217 -0
  137. package/src/discovery/feed.ts +172 -0
  138. package/src/discovery/indexnow.ts +114 -0
  139. package/src/discovery/llms.ts +79 -0
  140. package/src/discovery/markdown.ts +294 -0
  141. package/src/discovery/memory-cache.ts +97 -0
  142. package/src/discovery/middleware.ts +186 -0
  143. package/src/discovery/module.ts +353 -0
  144. package/src/discovery/news.ts +99 -0
  145. package/src/discovery/pack.ts +58 -0
  146. package/src/discovery/settings.ts +91 -0
  147. package/src/discovery/urls.ts +35 -0
  148. package/src/env.d.ts +10 -0
  149. package/src/files/card.ts +231 -0
  150. package/src/files/counts.ts +60 -0
  151. package/src/files/export.ts +44 -0
  152. package/src/files/format.ts +163 -0
  153. package/src/files/module.ts +596 -0
  154. package/src/files/pack.ts +92 -0
  155. package/src/files/r2-rules.ts +41 -0
  156. package/src/files/range.ts +50 -0
  157. package/src/files/s3.ts +114 -0
  158. package/src/files/serve.ts +141 -0
  159. package/src/files/sigv4.ts +160 -0
  160. package/src/files/site.ts +137 -0
  161. package/src/files/walker.ts +58 -0
  162. package/src/formUploads/lib.ts +266 -0
  163. package/src/formUploads/pack.ts +55 -0
  164. package/src/formUploads/wrap.ts +592 -0
  165. package/src/forms.ts +16 -0
  166. package/src/headings/pack.ts +154 -0
  167. package/src/headings/render.ts +139 -0
  168. package/src/headings/settings.ts +117 -0
  169. package/src/headings/slug.ts +68 -0
  170. package/src/headings/stamp.ts +142 -0
  171. package/src/headings/test-hooks.mjs +14 -0
  172. package/src/headings/toc.ts +48 -0
  173. package/src/images/cloudflare.ts +329 -0
  174. package/src/images/lib.ts +221 -0
  175. package/src/images/module.ts +372 -0
  176. package/src/images/pack.ts +804 -0
  177. package/src/images/pending.ts +37 -0
  178. package/src/images/settings.ts +48 -0
  179. package/src/images/setup.ts +192 -0
  180. package/src/images/variants-estimate.ts +162 -0
  181. package/src/images/variants-store.ts +475 -0
  182. package/src/images/variants.ts +311 -0
  183. package/src/index.ts +143 -0
  184. package/src/links/check.ts +101 -0
  185. package/src/links/classify.ts +270 -0
  186. package/src/links/export.ts +59 -0
  187. package/src/links/module.ts +652 -0
  188. package/src/links/pack.ts +43 -0
  189. package/src/links/pt.ts +325 -0
  190. package/src/links/store.ts +704 -0
  191. package/src/middleware.ts +287 -0
  192. package/src/modules.ts +53 -0
  193. package/src/options.ts +66 -0
  194. package/src/pageCache/lib.ts +274 -0
  195. package/src/pageCache/pack.ts +240 -0
  196. package/src/pageCache/usage-core.ts +152 -0
  197. package/src/pageCache/usage.ts +262 -0
  198. package/src/pageCache/warm.ts +722 -0
  199. package/src/redirects/middleware.ts +169 -0
  200. package/src/redirects/module.ts +124 -0
  201. package/src/redirects/pack.ts +89 -0
  202. package/src/redirects/removed-core.ts +151 -0
  203. package/src/redirects/removed.ts +293 -0
  204. package/src/redirects/rules.ts +355 -0
  205. package/src/redirects/slug-changes.ts +207 -0
  206. package/src/redirects/slug-core.ts +49 -0
  207. package/src/redirects/transfer.ts +92 -0
  208. package/src/reviews/lib.ts +624 -0
  209. package/src/reviews/pack.ts +135 -0
  210. package/src/reviews/schema.ts +128 -0
  211. package/src/reviews/settings.ts +26 -0
  212. package/src/robots/bots.ts +321 -0
  213. package/src/robots/data/bots.json +696 -0
  214. package/src/robots/data/verified.json +582 -0
  215. package/src/robots/directory.ts +26 -0
  216. package/src/robots/explain.ts +191 -0
  217. package/src/robots/guided.ts +300 -0
  218. package/src/robots/importer.ts +263 -0
  219. package/src/robots/middleware.ts +151 -0
  220. package/src/robots/module.ts +446 -0
  221. package/src/robots/pack.ts +69 -0
  222. package/src/robots/radar.ts +129 -0
  223. package/src/robots/rep.ts +362 -0
  224. package/src/robots/rules.ts +883 -0
  225. package/src/robots/transfer.ts +45 -0
  226. package/src/robots/validate.ts +524 -0
  227. package/src/schema/catalog.ts +146 -0
  228. package/src/schema/graph.ts +791 -0
  229. package/src/schema/module.ts +1138 -0
  230. package/src/schema/og-image.ts +100 -0
  231. package/src/schema/pack.ts +58 -0
  232. package/src/search/engine.ts +578 -0
  233. package/src/search/fallback.ts +80 -0
  234. package/src/search/live-cache.ts +116 -0
  235. package/src/search/live-client.ts +770 -0
  236. package/src/search/live-serve.ts +282 -0
  237. package/src/search/module.ts +261 -0
  238. package/src/search/pack.ts +57 -0
  239. package/src/search/query.ts +132 -0
  240. package/src/search/ratelimit-core.ts +118 -0
  241. package/src/search/ratelimit.ts +79 -0
  242. package/src/search/snippet.ts +135 -0
  243. package/src/search/title-index.ts +139 -0
  244. package/src/search/title-match.ts +192 -0
  245. package/src/shared.ts +71 -0
  246. package/src/version.ts +2 -0
  247. package/src/videos/date.ts +40 -0
  248. package/src/videos/engagement.ts +75 -0
  249. package/src/videos/lib.ts +679 -0
  250. package/src/videos/module.ts +1160 -0
  251. package/src/videos/pack.ts +187 -0
  252. package/src/videos/pause.ts +42 -0
  253. package/src/videos/poster.ts +324 -0
  254. package/src/videos/render.ts +336 -0
  255. package/src/videos/store.ts +551 -0
  256. package/src/videos/stream.ts +134 -0
  257. package/src/wpImport/convert.ts +702 -0
  258. package/src/wpImport/guests.ts +329 -0
  259. package/src/wpImport/gutenberg.ts +157 -0
  260. package/src/wpImport/markers.ts +66 -0
  261. package/src/wpImport/pack.ts +451 -0
  262. package/src/wpImport/parents.ts +202 -0
  263. package/src/wpImport/prepare.ts +592 -0
  264. package/src/wpImport/redirects.ts +358 -0
  265. package/src/wpImport/sources.ts +67 -0
  266. package/src/wpImport/stream.ts +160 -0
  267. package/src/wpImport/urls.ts +354 -0
@@ -0,0 +1,198 @@
1
+ # Migrating from WordPress
2
+
3
+ [Coywolf Pack documentation](README.md) · [Getting started](../README.md#getting-started)
4
+
5
+ The **WordPress import** module (**Plugins → WordPress import**, off until you turn it on under **Plugins → Coywolf Pack**) finishes a move from any WordPress site to EmDash. EmDash's own importer (**Settings → Import**) brings over posts, pages, custom post types, categories, tags, authors and media, but it has no plugin hook, and it drops or misses some things every WordPress site has. The module covers them with three parts:
6
+
7
+ - a **prepare** step that rewrites the export (WXR) before EmDash imports it, so what EmDash would drop survives as marker HTML blocks;
8
+ - a **converter** that runs on every save while the module is on (including each entry the importer creates) and turns those markers into native blocks, plus **Convert imported content** for entries already on the site;
9
+ - **tools for after the import**: co-author and guest-author bylines, category and page parents, old `/wp-content/` URLs, and redirects.
10
+
11
+ Sites that used Coywolf's WordPress plugins (Video Manager, Coywolf SEO, Custom Blocks, Coywolf Files, Guest Author) get more: see [Coywolf WordPress plugins](#coywolf-wordpress-plugins).
12
+
13
+ ## What EmDash's importer misses, and what closes the gap
14
+
15
+ | Gap | What happens on import | What closes it |
16
+ | --- | --- | --- |
17
+ | Heading `id`s | Dropped, so old `#links` break | Prepare + converter: each id becomes the heading's anchor (Headings & TOC → Heading anchors) |
18
+ | Reusable blocks (synced patterns) | The `core/block` reference is dropped | Prepare puts the pattern's blocks in its place, from the export's `wp_block` items |
19
+ | Core Details blocks | The summary is dropped | Prepare + converter: a Details block (Custom Blocks → Details) |
20
+ | Core Quote blocks | The quote's paragraphs are dropped; only the citation is kept | Prepare + converter: a Quote block with the paragraphs and citation (Custom Blocks → Quote); with Quote off, the quote stays HTML |
21
+ | Core Table captions (`<figcaption>`) | Dropped; the rows are kept | Prepare + converter: the table's `caption` field (plain text). Render it with `CaptionedTable` (see below) |
22
+ | Self-closing third-party blocks (all their content is in settings) | Dropped without a trace | Prepare lists them under **Blocks EmDash will drop**; rebuild them by hand |
23
+ | `&#91;` and bold markup in code blocks | Shown as literal text | Prepare cleans them |
24
+ | Co-authors and guest authors (Co-Authors Plus, PublishPress Authors) | Each post is credited to its WordPress user only | Step 3, **Co-authors and guest authors** |
25
+ | Category parents and page parents (`/{parent}/{child}/` URLs) | Every category is top level; pages lose their parent | Step 4, **Category and page parents**, with `{termpath:category}` and `{pagepath}` in [Content URLs](content-urls.md) |
26
+ | `/wp-content/` URLs EmDash's URL rewrite misses: other blocks' fields (a testimonial photo, a video poster or caption track), `-scaled`, `-rotated` and image-editor copies of large images, files that were never media-library attachments, theme and plugin files (since EmDash 1.2.0 its rewrite also covers HTML blocks, embeds, links in text and tables, file and button blocks, covers, and `-1024x683` size variants of attachments) | Still point at the old site, and break when it goes away | Step 5, **Old-site media URLs**: matches, imports, rewrites and redirects them |
27
+ | Old slugs (`_wp_old_slug`, which WordPress redirects by itself) and redirect plugins' rules (Redirection, Rank Math, Yoast SEO Premium, Coywolf SEO) | Not in the export | Step 6, **Redirects from WordPress** |
28
+ | `/wp-content/uploads/` URLs that other sites and image search link to | 404 | Step 5 adds a redirect for every old file URL it matched |
29
+ | Image width and height | Not recorded on imported image blocks | Theme: `imageDimensions(srcs)` from `@coywolf/emdash/astro` (see [In theme code](images.md#in-theme-code)) |
30
+ | Shortcodes, widgets, menus, theme settings, forms | Shortcodes stay as plain text; the rest isn't content | Rebuild by hand (see [Gaps to close by hand](#gaps-to-close-by-hand)) |
31
+
32
+ ## Before you import
33
+
34
+ Set the site up first, so imported content comes over connected:
35
+
36
+ 1. Install EmDash and Coywolf Pack ([Getting started](../README.md#getting-started)) and deploy. Set `EMDASH_ENCRYPTION_KEY` so the site can store API keys.
37
+ 2. Add API keys and tokens before importing, under **Plugins → Coywolf Pack → Settings** or on each module's page (for example the Stream API token on Videos, the AI Enrichment key, the Cloudflare API token for Clean Image URLs). Modules that read content as it's saved then work on imported entries right away.
38
+ 3. Turn on, under **Plugins → Coywolf Pack**: **WordPress import**; **Headings & TOC** with **Heading anchors**; **Custom Blocks** with the **Details** block; **Redirects** (and add its middleware, see [Redirects](redirects.md)); **Code Blocks** if the site has code. Coywolf plugin users turn on more (see below).
39
+ 4. Set `urls` in `coywolfPlugin()` to the permalink structure WordPress used (for example `posts: "/{termpath:category|uncategorized}/{slug}/"` for `/%category%/%postname%/`), so links, sitemaps and redirects use the same URLs. See [Content URLs](content-urls.md).
40
+ 5. Keep the WordPress site online until you finish: EmDash imports media from it, and step 5 downloads files that weren't in its media library. Back up the EmDash site before converting (see [Backups](backups.md)).
41
+
42
+ ## Step by step
43
+
44
+ Every step is on **Plugins → WordPress import**, in this order. Steps that change content have a **Dry run** that changes nothing, and running a step again changes nothing.
45
+
46
+ 1. **Export** from WordPress: **Tools → Export → All content**.
47
+ 2. **Prepare the WordPress export** (step 1): choose the file, check the report, and download the prepared copy. The file is processed in your browser and isn't uploaded. Or, from a checkout of this repository:
48
+
49
+ ```bash
50
+ node scripts/wp-prepare.mjs export.xml export-prepared.xml --redirects=old-slugs.json
51
+ ```
52
+
53
+ The report lists what changed per block (`core/heading → anchor`, `core/block → inlined`, …) and, separately, **Blocks EmDash will drop**: note those to rebuild after importing.
54
+ 3. **Import** the prepared file under **Settings → Import** with EmDash's importer, including its media (attachments) and its URL rewrite. Map WordPress authors to users as you like.
55
+ 4. **Convert imported content** (step 2) → **Dry run**. It should list nothing, because entries convert as they're imported. If the module (or a block) was off during the import, run **Convert**.
56
+ 5. **Co-authors and guest authors** (step 3) → **Dry run**, then **Create bylines and credit posts**.
57
+ 6. **Category and page parents** (step 4) → **Dry run**, then **Set category parents**. Paste the `pageParents` it shows into `coywolfPlugin()`, and use `{termpath:category}` and `{pagepath}` in `urls`.
58
+ 7. **Old-site media URLs** (step 5): **Find old URLs**, then **Import missing files**, then under **Rewrite URLs in content** a **Dry run** and **Rewrite URLs**, then **Add to Redirects** for the old file URLs.
59
+ 8. **Redirects from WordPress** (step 6): paste each redirect plugin's rules (commands below), **Build rules**, check the list and what was skipped, then **Add to Redirects** (or **Download JSON** and import it on the Redirects page).
60
+ 9. Close the [gaps to close by hand](#gaps-to-close-by-hand), then [verify](#verifying-the-move).
61
+ 10. When everything checks out, turn **WordPress import** off (nothing converts on save anymore; converted content stays).
62
+
63
+ ## Prepare and convert
64
+
65
+ EmDash converts Gutenberg with `@emdash-cms/gutenberg-to-portable-text`, which plugins can't extend: blocks it doesn't know become an HTML block of their saved HTML, and self-closing blocks (which keep everything in their settings) are dropped. Heading ids are dropped too. The prepare step rewrites, inside each entry's content only, what would be lost into HTML blocks holding a marker (`<div data-coywolf-wp="…" data-coywolf-attrs="…">`), which EmDash keeps, and leaves everything else byte for byte. While WordPress import is on, every save turns markers into native blocks; **Convert imported content** does the same for entries already on the site, draft-aware (an entry with unpublished changes is updated in its draft) and never overwriting an entry that changed while it ran.
66
+
67
+ | WordPress | Becomes | Notes |
68
+ | --- | --- | --- |
69
+ | Heading `id`s | the heading's anchor | Kept as written (no `jump-` prefix), so old `#links` work. Turn on Headings & TOC's anchors. |
70
+ | `core/block` (reusable block, synced pattern) | the pattern's blocks | Inlined from the export's `wp_block` items, up to 5 levels deep, and prepared like the rest. A pattern that isn't in the export is listed as `core/block (reusable block not in the export)`. Later edits to the pattern don't carry over (EmDash has no synced patterns). |
71
+ | core `details` | Details | Summary and content exactly; "open by default" kept. Needs the Custom Blocks Details block. |
72
+ | core `quote` | Quote | The paragraphs (formatting, links and the spaces between them kept) and the `<cite>` directly inside the quote as who said it (several are joined with commas). A `<cite>` inside a paragraph (a cited title) and a quote nested in it stay part of the quote. Needs the Custom Blocks Quote block; without it, a blockquote in an HTML block. |
73
+ | core `table` with a caption | EmDash table + `caption` | EmDash keeps the rows; the `<figcaption>` becomes a `caption` string on the table block (tags dropped, entities decoded). EmDash's editor keeps the field when it saves. EmDash's own table renderer ignores it: pass `CaptionedTable` from `@coywolf/emdash/astro` as the table renderer, `<PortableText value={value} components={{ type: { table: CaptionedTable } }} />`, which wraps a captioned table in `<figure class="cw-table">` with a `<figcaption>` (and renders tables without a caption as EmDash does). |
74
+ | `code` | EmDash code block | Language kept (Prism's `markup` → `html`); bold markup and `&#91;` inside code are cleaned |
75
+ | Yoast related links | HTML block | The markup WordPress rendered |
76
+ | `gravityforms/form` | empty marker (`gravity-form`, with `formId`) | Rebuild the form (EmDash forms plugin or theme) |
77
+ | Other self-closing third-party blocks | dropped by EmDash | Listed in the prepare report; nothing changes |
78
+
79
+ Everything else goes through EmDash's importer unchanged. Blocks from Coywolf's WordPress plugins are in [Coywolf WordPress plugins](#coywolf-wordpress-plugins).
80
+
81
+ ## Co-authors and guest authors
82
+
83
+ EmDash's importer credits each post to its WordPress user (`dc:creator`) and nothing else. **Co-authors and guest authors** (step 3) reads, from the export:
84
+
85
+ - **Co-Authors Plus**: the post's `author` terms (`cap-<login>`), with users' display names from the export's authors and guest authors' profiles from their `guest-author` entries (display name, website, bio, avatar);
86
+ - **PublishPress Authors**: the post's `author` terms, with profiles from term meta (`user_url`, `description`, `avatar`);
87
+ - **Coywolf Guest Author**: the guest in post meta (`_guest_author`, `_guest_author_url`, `_guest_author_bio`, `_guest_author_avatar_id`), which replaced the byline.
88
+
89
+ A post whose only author is its own WordPress user is left alone. Then, from your browser with EmDash's own byline and content API, as you:
90
+
91
+ 1. **Dry run** looks up, for each author, a byline with the same name (the importer creates one per WordPress user), the avatar in the media library (by file name; present when the importer imported the attachments), and each imported post (by its WordPress slug) with its current bylines.
92
+ 2. **Create bylines and credit posts** creates a guest byline (name, website, bio as plain text, avatar) for each author who has none, and sets each post's bylines to its authors in WordPress's order. Posts already credited are skipped. You need permission to manage bylines and edit any entry.
93
+
94
+ Schema & Social's author and Review schema then name the authors. What stays manual: an avatar that isn't in the media library (upload it, then pick it on the byline under **Bylines**); a post whose slug changed on import ("Not found" in the dry run; credit it in the editor).
95
+
96
+ ## Category and page parents
97
+
98
+ EmDash's importer creates every category at the top level and drops each page's parent, so WordPress URLs with parent categories (`/news/local/a-post/`) or parent pages (`/about/team/`) are lost. **Category and page parents** (step 4) puts them back, from your browser:
99
+
100
+ 1. It reads the categories (`<wp:category>` with `<wp:category_parent>`) and pages (`<wp:post_parent>`) from the export.
101
+ 2. **Dry run** lists each category that had a parent in WordPress with its parent on the site now: "Set to …", "Change to …", "Already set", or why it can't be set.
102
+ 3. **Set category parents** sets them through EmDash's own taxonomy API (`PUT /_emdash/api/taxonomies/category/terms/<slug>`), as you, parents first. You need permission to manage taxonomies.
103
+
104
+ EmDash pages have no parent, so the step shows a ready-to-paste `pageParents` option for `coywolfPlugin()` instead (use it with `{pagepath}`, see [Content URLs](content-urls.md)). `node scripts/wp-prepare.mjs` prints the same option, plus a `termParents` option you can use until the category parents are set.
105
+
106
+ ## Old-site media URLs
107
+
108
+ EmDash's importer imports attachments into the media library and rewrites their URLs in image, gallery, column and cover blocks, file and button blocks, embeds and HTML blocks, and links in text and tables (EmDash 1.2.0+), including `-1024x683` size variants of an attachment. **Old-site media URLs** (step 5) handles every other `/wp-content/uploads/`, `/wp-content/themes/` and `/wp-content/plugins/` URL of the old site:
109
+
110
+ 1. **Find old URLs** searches every entry (its latest draft included) for URLs on the old site's host names (filled in from the export; add a CDN host if media was served from one), protocol-relative and site-relative ones, URLs through Jetpack's image CDN (`i0.wp.com/<host>/…`), and URLs under the folder WordPress was installed in. It then reads the media library.
111
+ 2. Each URL is matched to a media library file. Size variants (`photo-1024x683.jpg`), `-scaled` and `-rotated` copies and image-editor copies (`photo-e1589912345678.jpg`) match the file imported from their original attachment, found through the export's attachment URLs (folder and name, so two `logo.png` in different months don't mix). A URL that wasn't an attachment, and theme and plugin files, are **not in the media library**; several media files with the same name are **ambiguous** (fix those in the editor). Attachments not used in content are included too, for redirects.
112
+ 3. **Import missing files** downloads those files from the old site into the media library with EmDash's own media importer (`POST /_emdash/api/import/wordpress/media`, which blocks private addresses and reuses a file with the same bytes, so running it again adds nothing). It needs the old site online and permission to import.
113
+ 4. **Rewrite URLs in content** → **Dry run**, then **Rewrite URLs** points every matched URL at its media file (`/_emdash/api/media/file/<key>`) in every entry's Portable Text (HTML blocks, links, text, and other blocks' fields) and text fields. Image and file fields are left to EmDash's rewrite. It runs the converter too.
114
+ 5. **Add to Redirects** adds a 301 from each old file path to its media file (the Redirects module handles file paths, which EmDash's built-in redirects skip), or **Download JSON** to import on the Redirects page.
115
+
116
+ Without the export loaded, uploads match by file name alone and unused attachments get no redirect. If the old site is already offline, upload the missing files under **Media** and fix the entries the URL table lists by hand.
117
+
118
+ ## Redirects from WordPress
119
+
120
+ **Redirects from WordPress** (step 6) builds Redirects module rules from:
121
+
122
+ - **Old slugs**: each published entry's `_wp_old_slug` values (WordPress redirected them by itself) become a 301 from the old URL to the entry's permalink in the export, both as WordPress built them. An old URL another entry uses now is skipped.
123
+ - Redirect plugins. Run on the WordPress server with WP-CLI and paste the output (change `wp_` if your tables use another prefix):
124
+
125
+ | Plugin | Command |
126
+ | --- | --- |
127
+ | Redirection | `wp db query "SELECT url, action_data, action_code, action_type, match_type, regex, status FROM wp_redirection_items"` |
128
+ | Rank Math | `wp db query "SELECT sources, url_to, header_code, status FROM wp_rank_math_redirections"` |
129
+ | Yoast SEO Premium | `wp option get wpseo-premium-redirects-base --format=json` |
130
+ | Coywolf SEO | `wp db query "SELECT source, target, type, is_regex FROM wp_coywolf_seo_redirects"` |
131
+
132
+ Absolute targets on the old site become site paths. Rank Math's exact, starts-with, ends-with, contains and regex sources each become a rule; Rank Math and Yoast patterns (stored without a leading slash) get one. 303 becomes 302, and 404/451 rules become 410 Gone (a 404 rule needs no redirect). Skipped, with the reason listed: disabled rules, rules with conditions (login state, referrer, user agent, …), sources with a query string, and patterns that aren't valid JavaScript regular expressions. A rule from a plugin wins over an old slug with the same source.
133
+
134
+ Old-slug rules point at the URLs WordPress used. If EmDash serves different URLs, set `urls` first (see [Before you import](#before-you-import)) or fix the targets on the Redirects page. What WordPress also did by itself and isn't carried over: guessing a post for a mistyped URL, `/?p=<id>` links, date-changed URLs (`_wp_old_date`), and archive URLs your theme doesn't serve (`/author/<name>/`, `/<year>/<month>/`, `/feed/`). Add rules for any that still get traffic (the Redirects page has a URL tester).
135
+
136
+ ## Gaps to close by hand
137
+
138
+ - **Blocks EmDash drops**: the prepare report lists them by block name. Rebuild each (a pack block, an embed, or HTML) in the entries that used it.
139
+ - **Image width and height**: imported image blocks have no size. In the theme, get sizes from the media library with `imageDimensions(srcs)` (one query per page), and set `width`/`height` (and `srcset`) on content images.
140
+ - **Shortcodes** stay as plain text (`[gallery …]`). Search content for each shortcode's name and replace it with a block.
141
+ - **Forms** (`gravity-form` markers, other form plugins): rebuild with the EmDash forms plugin or the theme.
142
+ - **Menus, widgets, theme settings**: not content; rebuild them in the theme and EmDash's menus.
143
+ - **Theme assets used by the theme itself** (CSS backgrounds, fonts): step 5 only finds those linked from content. Move the rest into the new theme.
144
+
145
+ ## Verifying the move
146
+
147
+ 1. **Convert imported content → Dry run** lists nothing.
148
+ 2. **Old-site media URLs → Find old URLs** shows 0 not in the media library and 0 ambiguous, and its **Rewrite URLs** dry run lists nothing.
149
+ 3. Every old URL still works. List WordPress's published URLs before it goes away, then check each on the new site (replace the host names):
150
+
151
+ ```bash
152
+ wp post list --post_type=post,page --post_status=publish --field=url > old-urls.txt
153
+ while read -r url; do
154
+ new="${url/https:\/\/old.coywolf.com/https://coywolf.com}"
155
+ code=$(curl -s -o /dev/null -w '%{http_code}' "$new")
156
+ [ "$code" = 200 ] || echo "$code $new"
157
+ done < old-urls.txt
158
+ ```
159
+
160
+ Anything not 200 needs a redirect or a `urls` fix. Run it with `curl -L` to follow redirects and check where they end up.
161
+ 4. Check a few old media URLs (a size variant and an attachment) with the URL tester on the Redirects page.
162
+ 5. Run **Link Manager** (if on) for broken internal links.
163
+ 6. Spot-check entries: heading anchors (`#links`), Details blocks, bylines, category and page URLs, and the Rich Results Test on an article.
164
+
165
+ ## Coywolf WordPress plugins
166
+
167
+ For sites that used Coywolf's WordPress plugins. These all run as part of the steps above (their blocks are converted by the same prepare step and converter), plus three extra steps (A–C) at the bottom of the WordPress import page.
168
+
169
+ **Before importing**, also turn on **Videos** (and Video schema, sitemap, plays and likes as wanted), **Reviews** and **Review schema**, **Custom Blocks** with the Note, Details, Affiliate disclosure, Quote, Testimonial and Podcast links blocks, **Headings & TOC** with the **Table of Contents block**, **File Downloads**, and **Code Blocks**. Set your disclosure wording and podcast links on the Custom Blocks page. Connect Stream on the Videos page (same account), or at least set the customer subdomain. Then paste Video Manager's and Coywolf Files' settings into **A. Player and download card defaults** (`wp option get coywolf_cvm_settings --format=json`, `wp option get coywolf_files_settings --format=json`) so converted blocks keep the site-wide choices.
170
+
171
+ | WordPress block | Becomes | Notes |
172
+ | --- | --- | --- |
173
+ | `coywolf-custom-blocks/cloudflare-stream` + the Custom HTML embed before it | Coywolf Video | Name, description, length (hours/minutes/seconds), player options from the iframe URL (autoplay, loop, muted, controls, preload, poster frame), size from the wrapper (padding-top %, max-width). Wrappers whose iframe was stripped on WordPress get their video back. |
174
+ | Custom HTML with only a Stream iframe or `<stream>` element | Coywolf Video | Same options; a `<figcaption>` becomes the shown description. Works for any site that embedded Cloudflare Stream this way. |
175
+ | `coywolf/video` (Video Manager) | Coywolf Video | Every block option; options the block didn't set take Video Manager's settings (step A; the plugin's defaults otherwise) |
176
+ | `coywolf-custom-blocks/review` | Coywolf Review | Item type from the Schema Type field, else Book when it has book details, Software application when it has operating systems or a category, else Product. Book and software details go into the new schema fields. Ratings import exactly (4.7 stays 4.7). |
177
+ | `coywolf-seo/table-of-contents` | Table of Contents | Levels, list style (disc → bulleted, decimal → numbered), title, show title, collapsible/collapsed |
178
+ | `coywolf/file` (Coywolf Files) | File download | Keeps the WordPress file id; see step B |
179
+ | `code` (Code Block Enhancer) | EmDash code block | As above |
180
+ | `coywolf-custom-blocks/sidenote`, `editorsnote` | Note | The text exactly as written. Sidenotes are Note-kind with WordPress's title "📌 Sidenote", editor's notes Editor's-note-kind with "📝 Editor's Note", both as H2. |
181
+ | `coywolf-custom-blocks/transcript`, `accordion` | Details | Summary (the transcript's default was "Read the audio transcript") and the hidden HTML exactly; transcripts use the Transcript style |
182
+ | `coywolf-custom-blocks/blockquote` | Quote | The quote, who said it (with its link) and the source URL from the `cite` field |
183
+ | `coywolf-custom-blocks/ftc` / `amazon` | Affiliate disclosure (affiliate / Amazon Associates) | They had no text of their own (the theme printed it), so they use the wording on the Custom Blocks page. Other self-closing disclosure blocks convert when you name them in **More affiliate disclosure blocks** (step 1) or with `--disclosure-block=` (coywolf.com used `genesis-custom-blocks/disclosure`). |
184
+ | `coywolf-custom-blocks/testimonial` | Testimonial | Name, title, quote, Social URL (the name's link) and Work URL (the title's link) exactly. The headshot keeps its WordPress URL until step 5 points it at the media library. |
185
+ | `coywolf-custom-blocks/podcast-rss` | Podcast links (the site's links) | The WordPress block had no fields: its template printed the show's links. Set them once on the Custom Blocks page. |
186
+ | `coywolf-custom-blocks/newsletter` | removed | Rendered nothing on WordPress |
187
+
188
+ Notes, details, quotes, disclosures, testimonials and podcast links convert only while their Custom Blocks switch is on. Otherwise their markers stay HTML blocks (with WordPress's markup, so the text shows) and convert when you turn the block on and run **Convert imported content** again. Markers from 0.10.0 and 0.11.0, and the `data-wb-block` markers of an earlier wellbeing.io import script (`cloudflare-stream`, `review`), convert too.
189
+
190
+ **After importing**, besides steps 1–6:
191
+
192
+ - **Guest authors** (Coywolf Guest Author plugin) are credited in step 3 with the other authors.
193
+ - **Coywolf SEO redirects** go in step 6 (or straight into **Redirects → Import**, see [Redirects](redirects.md)).
194
+ - **B. Coywolf Files downloads**: copy each Coywolf Files object into the bucket bound as `FILES` (or `MEDIA`) under the same key (`coywolf-files/YYYY/MM/<id>-<name>`), paste `wp db query "SELECT file_id, object_key, filename, mime, size, downloads, created FROM wp_coywolf_files"`, and set **Files → Settings → Download URL base** to WordPress's link base (`coywolf-file` by default) so old download links keep working. Download counts carry over.
195
+ - **C. Video Manager library**: paste the output of `wp option get coywolf_cvm_descriptions --format=json` (and the same for `coywolf_cvm_posters` and `coywolf_cvm_downloads`) for per-video descriptions, posters and MP4 links. On the Videos page, **Refresh** the library (with the token) and **Rebuild embed index**.
196
+ - Run the Headings & TOC, Schema and Videos checks on a few entries (Rich Results Test for a review and a video page).
197
+
198
+ Without a Stream token, converted videos still play and have VideoObject schema: the name, length, upload date and size come from WordPress and are stored as the video's details until Stream's own data replaces them. Captions, plays from Stream, MP4 links found via the API, and the library listing need the token.
package/package.json CHANGED
@@ -1,6 +1,80 @@
1
1
  {
2
- "name": "@coywolf/emdash",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@coywolf/emdash",
3
+ "version": "0.35.14",
4
+ "description": "Coywolf Pack for EmDash: one plugin with Coywolf's features for EmDash sites on Cloudflare. Full backups and restore, a redirect manager, and more to come.",
5
+ "license": "MIT",
6
+ "author": "Coywolf LLC (https://coywolf.com)",
7
+ "homepage": "https://github.com/coywolf-llc/coywolf-pack",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/coywolf-llc/coywolf-pack.git"
11
+ },
12
+ "type": "module",
13
+ "keywords": [
14
+ "emdash",
15
+ "emdash-plugin",
16
+ "coywolf",
17
+ "backup",
18
+ "restore",
19
+ "redirects",
20
+ "cloudflare",
21
+ "d1",
22
+ "r2"
23
+ ],
24
+ "main": "./src/index.ts",
25
+ "exports": {
26
+ ".": "./src/index.ts",
27
+ "./admin": "./src/admin/index.tsx",
28
+ "./astro": "./src/astro/index.ts",
29
+ "./middleware": "./src/middleware.ts",
30
+ "./forms": "./src/forms.ts"
31
+ },
32
+ "files": [
33
+ "src",
34
+ "!src/**/*.test.ts",
35
+ "!src/**/*.test.mjs",
36
+ "README.md",
37
+ "LICENSE",
38
+ "docs"
39
+ ],
40
+ "scripts": {
41
+ "typecheck": "tsc --noEmit",
42
+ "test": "node --import ./test/ts-resolve.mjs --test \"test/**/*.test.mjs\" \"src/**/*.test.ts\" \"src/**/*.test.mjs\""
43
+ },
44
+ "peerDependencies": {
45
+ "@cloudflare/kumo": "*",
46
+ "@emdash-cms/plugin-forms": ">=0.2.9",
47
+ "@phosphor-icons/react": "^2.1.0",
48
+ "@tanstack/react-query": "^5.0.0",
49
+ "astro": ">=6.0.0",
50
+ "emdash": ">=1.2.0",
51
+ "highlight.js": "^11.0.0",
52
+ "lowlight": "^3.0.0",
53
+ "react": "^18.0.0 || ^19.0.0",
54
+ "react-dom": "^18.0.0 || ^19.0.0",
55
+ "zod": "^3.24.0 || ^4.0.0"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "@emdash-cms/plugin-forms": {
59
+ "optional": true
60
+ }
61
+ },
62
+ "devDependencies": {
63
+ "@cloudflare/kumo": "*",
64
+ "@cloudflare/workers-types": "^4.0.0",
65
+ "@phosphor-icons/react": "^2.1.0",
66
+ "@tanstack/react-query": "^5.0.0",
67
+ "@types/react": "^19.0.0",
68
+ "astro": "^7.3.0",
69
+ "emdash": "^1.3.0",
70
+ "react": "^19.0.0",
71
+ "typescript": "^5.9.0",
72
+ "zod": "^4.6.0"
73
+ },
74
+ "publishConfig": {
75
+ "access": "public"
76
+ },
77
+ "bugs": {
78
+ "url": "https://github.com/coywolf-llc/coywolf-pack/issues"
79
+ }
80
+ }
@@ -0,0 +1,57 @@
1
+ // The admin pages render inside EmDash's admin, whose stylesheet is a precompiled
2
+ // Tailwind build: a utility class it doesn't already contain does nothing. For the
3
+ // layouts it has no class for (responsive variants, arbitrary grid templates, sticky
4
+ // panels), the pack ships these rules under its own cw- names, so they can't collide
5
+ // with a Tailwind class a future EmDash build adds. Breakpoints match Tailwind's
6
+ // (sm 40rem, md 48rem, lg 64rem) and colors use EmDash's kumo tokens.
7
+ //
8
+ // These rules are unlayered, so they win over EmDash's @layer utilities: each class
9
+ // carries its own hidden state (cw-lg-block is display:none below lg) rather than
10
+ // pairing with Tailwind's `hidden`.
11
+ //
12
+ // test/admin-classes.test.mjs fails when an admin className is in neither EmDash's
13
+ // stylesheet nor this file.
14
+
15
+ export const SAVE_BAR_CSS = `@keyframes cw-save-bar-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
16
+ .cw-save-bar{animation:cw-save-bar-in 160ms ease-out}
17
+ @media (prefers-reduced-motion: reduce){.cw-save-bar{animation:none}}`;
18
+
19
+ export const ADMIN_CSS = `.cw-fill-tint{fill:var(--color-kumo-tint)}
20
+ .cw-stroke-line{stroke:var(--color-kumo-line)}
21
+ .cw-accent-current{accent-color:currentColor}
22
+ .cw-field-icon{bottom:.625rem}
23
+ .cw-w-60{width:15rem}
24
+ .cw-overflow-auto{overflow:auto}
25
+ .cw-ps-6{padding-inline-start:1.5rem}
26
+ .cw-md-flex,.cw-md-table-cell,.cw-lg-block{display:none}
27
+ @media (min-width:40rem){
28
+ .cw-sm-cols-8-fill{grid-template-columns:8rem minmax(0,1fr)}
29
+ }
30
+ @media (min-width:48rem){
31
+ .cw-md-flex{display:flex}
32
+ .cw-md-row{flex-direction:row;align-items:center}
33
+ .cw-md-w-60{width:15rem}
34
+ .cw-md-table-cell{display:table-cell}
35
+ }
36
+ @media (min-width:64rem){
37
+ .cw-lg-block{display:block}
38
+ .cw-lg-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}
39
+ .cw-lg-cols-20-fill{grid-template-columns:minmax(0,20rem) minmax(0,1fr)}
40
+ .cw-lg-cols-24-fill{grid-template-columns:minmax(0,24rem) minmax(0,1fr)}
41
+ .cw-lg-cols-fill-24{grid-template-columns:minmax(0,1fr) minmax(0,24rem);align-items:start}
42
+ .cw-lg-sticky{position:sticky;top:1rem}
43
+ }`;
44
+
45
+ const STYLE_ID = "cw-pack-admin-css";
46
+
47
+ /** Adds the pack's admin CSS to the page once (a no-op outside the browser). */
48
+ export function injectAdminCss(): void {
49
+ if (typeof document === "undefined") return;
50
+ let style = document.getElementById(STYLE_ID);
51
+ if (!style) {
52
+ style = document.createElement("style");
53
+ style.id = STYLE_ID;
54
+ document.head.appendChild(style);
55
+ }
56
+ if (style.textContent !== ADMIN_CSS) style.textContent = ADMIN_CSS;
57
+ }