emdash 0.30.0 → 0.31.1

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 (392) hide show
  1. package/dist/api/route-utils.mjs +13 -13
  2. package/dist/api/schemas/index.d.mts +1 -1
  3. package/dist/api/schemas/index.mjs +3 -3
  4. package/dist/{api-b8WIiGU4.mjs → api-BkqFzIu4.mjs} +23 -23
  5. package/dist/api-BkqFzIu4.mjs.map +1 -0
  6. package/dist/{apply-CmIJK9j8.mjs → apply-DuXsqll7.mjs} +26 -25
  7. package/dist/apply-DuXsqll7.mjs.map +1 -0
  8. package/dist/astro/index.d.mts +2 -2
  9. package/dist/astro/index.mjs +1 -1
  10. package/dist/astro/middleware/auth.d.mts +2 -2
  11. package/dist/astro/middleware/auth.mjs +2 -2
  12. package/dist/astro/middleware/redirect.mjs +5 -5
  13. package/dist/astro/middleware/request-context.mjs +1 -1
  14. package/dist/astro/middleware.d.mts +2 -2
  15. package/dist/astro/middleware.mjs +146 -70
  16. package/dist/astro/middleware.mjs.map +1 -1
  17. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
  18. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
  19. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
  20. package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
  21. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
  22. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +7 -7
  23. package/dist/astro/routes/api/admin/byline-fields/index.mjs +7 -7
  24. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +7 -7
  25. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -14
  26. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -14
  27. package/dist/astro/routes/api/admin/bylines/index.d.mts.map +1 -1
  28. package/dist/astro/routes/api/admin/bylines/index.mjs +18 -17
  29. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  30. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +10 -10
  31. package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
  32. package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
  33. package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
  34. package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
  35. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
  36. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
  37. package/dist/astro/routes/api/admin/media-usage/repair.mjs +10 -10
  38. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  39. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  40. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +34 -34
  41. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +34 -34
  42. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +34 -34
  43. package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
  44. package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +34 -34
  45. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +34 -34
  46. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +34 -34
  47. package/dist/astro/routes/api/admin/plugins/index.mjs +34 -34
  48. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
  49. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +34 -34
  50. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +34 -34
  51. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +34 -34
  52. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +34 -34
  53. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +35 -35
  54. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +34 -34
  55. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +35 -35
  56. package/dist/astro/routes/api/admin/plugins/updates.mjs +34 -34
  57. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +34 -34
  58. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
  59. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +34 -34
  60. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
  61. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
  62. package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
  63. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  64. package/dist/astro/routes/api/admin/users/index.mjs +6 -6
  65. package/dist/astro/routes/api/auth/dev-bypass.mjs +4 -4
  66. package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
  67. package/dist/astro/routes/api/auth/invite/complete.mjs +6 -6
  68. package/dist/astro/routes/api/auth/invite/index.mjs +6 -6
  69. package/dist/astro/routes/api/auth/invite/register-options.mjs +6 -6
  70. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  71. package/dist/astro/routes/api/auth/magic-link/send.mjs +7 -7
  72. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  73. package/dist/astro/routes/api/auth/me.mjs +7 -7
  74. package/dist/astro/routes/api/auth/mode.mjs +2 -2
  75. package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
  76. package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
  77. package/dist/astro/routes/api/auth/passkey/options.mjs +7 -7
  78. package/dist/astro/routes/api/auth/passkey/register/options.mjs +6 -6
  79. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +6 -6
  80. package/dist/astro/routes/api/auth/passkey/verify.mjs +6 -6
  81. package/dist/astro/routes/api/auth/signup/complete.mjs +6 -6
  82. package/dist/astro/routes/api/auth/signup/request.mjs +7 -7
  83. package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
  84. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +10 -10
  85. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +10 -10
  86. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
  87. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
  88. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
  89. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
  90. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +9 -9
  91. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +7 -7
  92. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +14 -14
  93. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +14 -14
  94. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
  95. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
  96. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +7 -7
  97. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +13 -13
  98. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
  99. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
  100. package/dist/astro/routes/api/content/_collection_/_id_.mjs +7 -7
  101. package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
  102. package/dist/astro/routes/api/content/_collection_/index.mjs +7 -7
  103. package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
  104. package/dist/astro/routes/api/dashboard.mjs +7 -7
  105. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  106. package/dist/astro/routes/api/import/probe.mjs +12 -12
  107. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  108. package/dist/astro/routes/api/import/wordpress/execute.d.mts +2 -2
  109. package/dist/astro/routes/api/import/wordpress/execute.mjs +11 -11
  110. package/dist/astro/routes/api/import/wordpress/media.d.mts +2 -2
  111. package/dist/astro/routes/api/import/wordpress/media.mjs +7 -7
  112. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
  113. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +2 -2
  114. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +9 -9
  115. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +12 -12
  116. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +2 -2
  117. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +19 -19
  118. package/dist/astro/routes/api/manifest.mjs +3 -3
  119. package/dist/astro/routes/api/mcp.mjs +39 -39
  120. package/dist/astro/routes/api/media/_id_/confirm.mjs +7 -7
  121. package/dist/astro/routes/api/media/_id_/usage.mjs +11 -11
  122. package/dist/astro/routes/api/media/_id_.mjs +10 -10
  123. package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
  124. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
  125. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
  126. package/dist/astro/routes/api/media/providers/index.mjs +3 -3
  127. package/dist/astro/routes/api/media/upload-url.mjs +7 -7
  128. package/dist/astro/routes/api/media.mjs +10 -10
  129. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
  130. package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
  131. package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
  132. package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
  133. package/dist/astro/routes/api/menus/_name_.mjs +8 -8
  134. package/dist/astro/routes/api/menus/index.mjs +8 -8
  135. package/dist/astro/routes/api/oauth/authorize.d.mts.map +1 -1
  136. package/dist/astro/routes/api/oauth/authorize.mjs +23 -9
  137. package/dist/astro/routes/api/oauth/authorize.mjs.map +1 -1
  138. package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
  139. package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
  140. package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
  141. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  142. package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
  143. package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
  144. package/dist/astro/routes/api/oauth/token.mjs +2 -2
  145. package/dist/astro/routes/api/openapi.json.mjs +4 -4
  146. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +4 -4
  147. package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
  148. package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
  149. package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
  150. package/dist/astro/routes/api/redirects/index.mjs +11 -11
  151. package/dist/astro/routes/api/relations/_id_/index.mjs +14 -14
  152. package/dist/astro/routes/api/relations/_id_/translations.mjs +10 -10
  153. package/dist/astro/routes/api/relations/index.mjs +14 -14
  154. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
  155. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
  156. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +34 -34
  157. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +34 -34
  158. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +34 -34
  159. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +34 -34
  160. package/dist/astro/routes/api/schema/collections/index.mjs +34 -34
  161. package/dist/astro/routes/api/schema/index.mjs +8 -8
  162. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +34 -34
  163. package/dist/astro/routes/api/schema/orphans/index.mjs +34 -34
  164. package/dist/astro/routes/api/search/enable.mjs +11 -11
  165. package/dist/astro/routes/api/search/index.mjs +10 -10
  166. package/dist/astro/routes/api/search/rebuild.mjs +11 -11
  167. package/dist/astro/routes/api/search/stats.mjs +7 -7
  168. package/dist/astro/routes/api/search/suggest.mjs +10 -10
  169. package/dist/astro/routes/api/sections/_slug_.mjs +10 -10
  170. package/dist/astro/routes/api/sections/index.mjs +10 -10
  171. package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +4 -4
  172. package/dist/astro/routes/api/settings/backups/archives/index.mjs +4 -4
  173. package/dist/astro/routes/api/settings/backups/export.mjs +4 -4
  174. package/dist/astro/routes/api/settings/backups/index.mjs +5 -5
  175. package/dist/astro/routes/api/settings/email.mjs +4 -4
  176. package/dist/astro/routes/api/settings.mjs +12 -12
  177. package/dist/astro/routes/api/setup/admin-verify.mjs +6 -6
  178. package/dist/astro/routes/api/setup/admin.mjs +6 -6
  179. package/dist/astro/routes/api/setup/dev-bypass.mjs +18 -18
  180. package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
  181. package/dist/astro/routes/api/setup/index.mjs +22 -22
  182. package/dist/astro/routes/api/setup/status.mjs +3 -3
  183. package/dist/astro/routes/api/snapshot.mjs +4 -4
  184. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +13 -13
  185. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +13 -13
  186. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +13 -13
  187. package/dist/astro/routes/api/taxonomies/index.mjs +13 -13
  188. package/dist/astro/routes/api/themes/preview.mjs +4 -4
  189. package/dist/astro/routes/api/typegen.mjs +5 -5
  190. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
  191. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +10 -10
  192. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +10 -10
  193. package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -6
  194. package/dist/astro/routes/api/widget-areas/index.mjs +10 -10
  195. package/dist/astro/routes/api/widget-components.mjs +2 -2
  196. package/dist/astro/routes/robots.txt.mjs +5 -5
  197. package/dist/astro/routes/sitemap-_collection_.xml.mjs +7 -7
  198. package/dist/astro/routes/sitemap.xml.mjs +5 -5
  199. package/dist/astro/types.d.mts +2 -2
  200. package/dist/{authorize-WxsiePzy.mjs → authorize-6WPZa1xZ.mjs} +2 -2
  201. package/dist/{authorize-WxsiePzy.mjs.map → authorize-6WPZa1xZ.mjs.map} +1 -1
  202. package/dist/{backup-S8kFWtwD.mjs → backup-DoW-H0r4.mjs} +2 -2
  203. package/dist/{backup-S8kFWtwD.mjs.map → backup-DoW-H0r4.mjs.map} +1 -1
  204. package/dist/{byline-XEjchwzZ.mjs → byline-BTOxUlFF.mjs} +6 -6
  205. package/dist/{byline-XEjchwzZ.mjs.map → byline-BTOxUlFF.mjs.map} +1 -1
  206. package/dist/{bylines-czseViYo.mjs → bylines-Bc-3xtpz.mjs} +6 -6
  207. package/dist/{bylines-czseViYo.mjs.map → bylines-Bc-3xtpz.mjs.map} +1 -1
  208. package/dist/{bylines-BJbT4gKS.mjs → bylines-oVN0iPwX.mjs} +12 -8
  209. package/dist/bylines-oVN0iPwX.mjs.map +1 -0
  210. package/dist/{cache-CGCd6AVM.mjs → cache-DDwy3JDW.mjs} +2 -2
  211. package/dist/{cache-CGCd6AVM.mjs.map → cache-DDwy3JDW.mjs.map} +1 -1
  212. package/dist/{chunks-BxXyunY-.mjs → chunks-Bq-5DPrD.mjs} +2 -2
  213. package/dist/{chunks-BxXyunY-.mjs.map → chunks-Bq-5DPrD.mjs.map} +1 -1
  214. package/dist/cli/index.mjs +18 -18
  215. package/dist/{comment-DPT0WKyd.mjs → comment-B9cUud8H.mjs} +2 -2
  216. package/dist/{comment-DPT0WKyd.mjs.map → comment-B9cUud8H.mjs.map} +1 -1
  217. package/dist/{comment-reaction-C65MldIB.mjs → comment-reaction-VUXO981F.mjs} +2 -2
  218. package/dist/{comment-reaction-C65MldIB.mjs.map → comment-reaction-VUXO981F.mjs.map} +1 -1
  219. package/dist/{comments-Bz6sCbgD.mjs → comments-PXj1E9Si.mjs} +3 -3
  220. package/dist/{comments-Bz6sCbgD.mjs.map → comments-PXj1E9Si.mjs.map} +1 -1
  221. package/dist/{config-CVssduLe.mjs → config-B6b_BobR.mjs} +6 -2
  222. package/dist/config-B6b_BobR.mjs.map +1 -0
  223. package/dist/{content-Ci04z2z-.mjs → content-BC43pICM.mjs} +4 -4
  224. package/dist/{content-Ci04z2z-.mjs.map → content-BC43pICM.mjs.map} +1 -1
  225. package/dist/{content-refresh-D4khvC0R.mjs → content-refresh-CPMSauD3.mjs} +4 -4
  226. package/dist/{content-refresh-D4khvC0R.mjs.map → content-refresh-CPMSauD3.mjs.map} +1 -1
  227. package/dist/{context-B6hc7zJL.mjs → context-DOurRIrb.mjs} +10 -10
  228. package/dist/{context-B6hc7zJL.mjs.map → context-DOurRIrb.mjs.map} +1 -1
  229. package/dist/{dashboard-C5NkXFbi.mjs → dashboard-CBm4zhc3.mjs} +4 -4
  230. package/dist/{dashboard-C5NkXFbi.mjs.map → dashboard-CBm4zhc3.mjs.map} +1 -1
  231. package/dist/database/pg-migration-lock.d.mts.map +1 -1
  232. package/dist/database/pg-migration-lock.mjs +1 -4
  233. package/dist/database/pg-migration-lock.mjs.map +1 -1
  234. package/dist/db/index.mjs +2 -2
  235. package/dist/{dialect-helpers-HxEQGl_1.mjs → dialect-helpers-BtOwo2us.mjs} +4 -4
  236. package/dist/dialect-helpers-BtOwo2us.mjs.map +1 -0
  237. package/dist/{error-CEGF6UZb.mjs → error-DJ98TRbQ.mjs} +2 -2
  238. package/dist/{error-CEGF6UZb.mjs.map → error-DJ98TRbQ.mjs.map} +1 -1
  239. package/dist/{field-defs-cache-DvmlgP-D.mjs → field-defs-cache-KfNJ_qLQ.mjs} +2 -2
  240. package/dist/{field-defs-cache-DvmlgP-D.mjs.map → field-defs-cache-KfNJ_qLQ.mjs.map} +1 -1
  241. package/dist/{fts-manager-DzqIBrrW.mjs → fts-manager-hyCU9OOp.mjs} +3 -3
  242. package/dist/{fts-manager-DzqIBrrW.mjs.map → fts-manager-hyCU9OOp.mjs.map} +1 -1
  243. package/dist/{hreflang-db2Tpb2o.mjs → hreflang-CbYeCzl9.mjs} +7 -7
  244. package/dist/{hreflang-db2Tpb2o.mjs.map → hreflang-CbYeCzl9.mjs.map} +1 -1
  245. package/dist/{import-Dmkm8S1W.mjs → import-oDYCgNYQ.mjs} +2 -2
  246. package/dist/{import-Dmkm8S1W.mjs.map → import-oDYCgNYQ.mjs.map} +1 -1
  247. package/dist/{index-wYnC6u4o.d.mts → index-BZJ_5FOX.d.mts} +50 -4
  248. package/dist/index-BZJ_5FOX.d.mts.map +1 -0
  249. package/dist/index.d.mts +2 -2
  250. package/dist/index.mjs +48 -48
  251. package/dist/{load-Cx27ki1l.mjs → load-Dg5gtWma.mjs} +2 -2
  252. package/dist/{load-Cx27ki1l.mjs.map → load-Dg5gtWma.mjs.map} +1 -1
  253. package/dist/{loader-Be3ouI5L.mjs → loader-D4rigvjR.mjs} +4 -4
  254. package/dist/{loader-Be3ouI5L.mjs.map → loader-D4rigvjR.mjs.map} +1 -1
  255. package/dist/media/local-runtime.d.mts +2 -2
  256. package/dist/media/local-runtime.mjs +5 -5
  257. package/dist/{media-kIV1IxFf.mjs → media-BJ6SRY3g.mjs} +8 -4
  258. package/dist/media-BJ6SRY3g.mjs.map +1 -0
  259. package/dist/{media-BjhhENaJ.mjs → media-its1WLYv.mjs} +2 -2
  260. package/dist/{media-BjhhENaJ.mjs.map → media-its1WLYv.mjs.map} +1 -1
  261. package/dist/{media-upload-D2wk3EIt.mjs → media-upload-DNu3QySc.mjs} +4 -4
  262. package/dist/{media-upload-D2wk3EIt.mjs.map → media-upload-DNu3QySc.mjs.map} +1 -1
  263. package/dist/{media-usage-CljdO1mc.mjs → media-usage-DPQ3K4xB.mjs} +6 -6
  264. package/dist/{media-usage-CljdO1mc.mjs.map → media-usage-DPQ3K4xB.mjs.map} +1 -1
  265. package/dist/{menus-CZyG6rvx.mjs → menus-BMGzWyWH.mjs} +15 -8
  266. package/dist/menus-BMGzWyWH.mjs.map +1 -0
  267. package/dist/{menus-BcyElFtI.mjs → menus-CPf_SW7u.mjs} +86 -8
  268. package/dist/menus-CPf_SW7u.mjs.map +1 -0
  269. package/dist/{parse-C_-6klII.mjs → parse-53zyxlpv.mjs} +2 -2
  270. package/dist/{parse-C_-6klII.mjs.map → parse-53zyxlpv.mjs.map} +1 -1
  271. package/dist/plugin-utils.d.mts +2 -2
  272. package/dist/plugins/adapt-sandbox-entry.d.mts +2 -2
  273. package/dist/{query-DR73ZNfm.mjs → query-Bz9bWZfm.mjs} +14 -14
  274. package/dist/{query-DR73ZNfm.mjs.map → query-Bz9bWZfm.mjs.map} +1 -1
  275. package/dist/{query-Di7DOmPV.mjs → query-DdFGHzQF.mjs} +3 -3
  276. package/dist/{query-Di7DOmPV.mjs.map → query-DdFGHzQF.mjs.map} +1 -1
  277. package/dist/{rate-limit-CMj83JNW.mjs → rate-limit-D3IEiUi2.mjs} +2 -2
  278. package/dist/{rate-limit-CMj83JNW.mjs.map → rate-limit-D3IEiUi2.mjs.map} +1 -1
  279. package/dist/{redirect-CgLPYflR.mjs → redirect-BoUac4NT.mjs} +3 -3
  280. package/dist/{redirect-CgLPYflR.mjs.map → redirect-BoUac4NT.mjs.map} +1 -1
  281. package/dist/{redirects-B7t9bKsL.mjs → redirects-ChbpqKiS.mjs} +3 -3
  282. package/dist/{redirects-B7t9bKsL.mjs.map → redirects-ChbpqKiS.mjs.map} +1 -1
  283. package/dist/{redirects-BBq3n2Rj.mjs → redirects-D457qwt0.mjs} +8 -8
  284. package/dist/redirects-D457qwt0.mjs.map +1 -0
  285. package/dist/{registry-FV15nLge.mjs → registry-y21kjhfG.mjs} +6 -6
  286. package/dist/{registry-FV15nLge.mjs.map → registry-y21kjhfG.mjs.map} +1 -1
  287. package/dist/{relation-9M9j_UWo.mjs → relation-Du0r04E0.mjs} +3 -3
  288. package/dist/{relation-9M9j_UWo.mjs.map → relation-Du0r04E0.mjs.map} +1 -1
  289. package/dist/{relations-5_avdrN_.mjs → relations-CZMpBqEd.mjs} +3 -3
  290. package/dist/{relations-5_avdrN_.mjs.map → relations-CZMpBqEd.mjs.map} +1 -1
  291. package/dist/{relations-BKvO39-9.mjs → relations-DtX1UMBH.mjs} +13 -7
  292. package/dist/relations-DtX1UMBH.mjs.map +1 -0
  293. package/dist/{relations-Bh8jlFsU.d.mts → relations-qKmSj9KM.d.mts} +27 -23
  294. package/dist/relations-qKmSj9KM.d.mts.map +1 -0
  295. package/dist/{resolve-Cd9dzclN.mjs → resolve-DFr4kq72.mjs} +2 -2
  296. package/dist/{resolve-Cd9dzclN.mjs.map → resolve-DFr4kq72.mjs.map} +1 -1
  297. package/dist/{runner-DfnZ5eUr.mjs → runner-BdeLEgad.mjs} +3 -3
  298. package/dist/{runner-DfnZ5eUr.mjs.map → runner-BdeLEgad.mjs.map} +1 -1
  299. package/dist/runtime.d.mts +2 -2
  300. package/dist/runtime.mjs +3 -3
  301. package/dist/{schema-BXxlHeAf.mjs → schema-BH8P8vaC.mjs} +3 -3
  302. package/dist/{schema-BXxlHeAf.mjs.map → schema-BH8P8vaC.mjs.map} +1 -1
  303. package/dist/{scopes-Bl4IwHA-.mjs → scopes-DTIkIaGA.mjs} +2 -2
  304. package/dist/{scopes-Bl4IwHA-.mjs.map → scopes-DTIkIaGA.mjs.map} +1 -1
  305. package/dist/{search-Bff-7jFt.mjs → search-D-Yhpmvs.mjs} +8 -7
  306. package/dist/search-D-Yhpmvs.mjs.map +1 -0
  307. package/dist/{secrets-CSwQIl4q.mjs → secrets-CQ-69kF9.mjs} +23 -17
  308. package/dist/secrets-CQ-69kF9.mjs.map +1 -0
  309. package/dist/{sections-CwW4s1al.mjs → sections-CzVBAKjw.mjs} +3 -3
  310. package/dist/{sections-CwW4s1al.mjs.map → sections-CzVBAKjw.mjs.map} +1 -1
  311. package/dist/seed/index.mjs +16 -16
  312. package/dist/seo/index.mjs +12 -12
  313. package/dist/{seo-2cmFHZns.mjs → seo-kJzVAmmu.mjs} +2 -2
  314. package/dist/{seo-2cmFHZns.mjs.map → seo-kJzVAmmu.mjs.map} +1 -1
  315. package/dist/{service-DkGTWGIi.mjs → service-CQnc276a.mjs} +2 -2
  316. package/dist/{service-DkGTWGIi.mjs.map → service-CQnc276a.mjs.map} +1 -1
  317. package/dist/{settings-CpA4lQFt.mjs → settings-C2PaBS49.mjs} +3 -3
  318. package/dist/{settings-CpA4lQFt.mjs.map → settings-C2PaBS49.mjs.map} +1 -1
  319. package/dist/{settings-C4s8hFQm.mjs → settings-Dtc0DU31.mjs} +3 -3
  320. package/dist/{settings-C4s8hFQm.mjs.map → settings-Dtc0DU31.mjs.map} +1 -1
  321. package/dist/{taxonomies-Ce49uIzY.mjs → taxonomies-BN6M9Nrv.mjs} +26 -17
  322. package/dist/taxonomies-BN6M9Nrv.mjs.map +1 -0
  323. package/dist/{taxonomies-DjSKBZpq.mjs → taxonomies-r7pqLBCn.mjs} +53 -24
  324. package/dist/taxonomies-r7pqLBCn.mjs.map +1 -0
  325. package/dist/{taxonomy-DfVooU4W.mjs → taxonomy-0mCI25HN.mjs} +3 -3
  326. package/dist/{taxonomy-DfVooU4W.mjs.map → taxonomy-0mCI25HN.mjs.map} +1 -1
  327. package/dist/{types-D1iJ3DpO.mjs → types-CrRUFdej.mjs} +2 -2
  328. package/dist/{types-D1iJ3DpO.mjs.map → types-CrRUFdej.mjs.map} +1 -1
  329. package/dist/{user-Bh-L1qo6.mjs → user-aTXqmXBz.mjs} +3 -3
  330. package/dist/{user-Bh-L1qo6.mjs.map → user-aTXqmXBz.mjs.map} +1 -1
  331. package/dist/{validate-V9nCwq_-.mjs → validate-CDT1jl6_.mjs} +11 -6
  332. package/dist/validate-CDT1jl6_.mjs.map +1 -0
  333. package/dist/validate-DULfgpJP.d.mts.map +1 -1
  334. package/dist/{validation-BsVUJfsP.mjs → validation-DlD0E35W.mjs} +8 -8
  335. package/dist/{validation-BsVUJfsP.mjs.map → validation-DlD0E35W.mjs.map} +1 -1
  336. package/dist/version-BRMngh9I.mjs +7 -0
  337. package/dist/{version-907opKac.mjs.map → version-BRMngh9I.mjs.map} +1 -1
  338. package/dist/{widgets-DGv1Z04V.mjs → widgets-5fipNkMT.mjs} +2 -2
  339. package/dist/{widgets-DGv1Z04V.mjs.map → widgets-5fipNkMT.mjs.map} +1 -1
  340. package/dist/{wxr-taxonomies-Cs1N6O8z.mjs → wxr-taxonomies-DrihupaC.mjs} +4 -4
  341. package/dist/{wxr-taxonomies-Cs1N6O8z.mjs.map → wxr-taxonomies-DrihupaC.mjs.map} +1 -1
  342. package/dist/{zod-generator-B5prQ5M4.mjs → zod-generator-CwkHWz4f.mjs} +2 -2
  343. package/dist/{zod-generator-B5prQ5M4.mjs.map → zod-generator-CwkHWz4f.mjs.map} +1 -1
  344. package/package.json +7 -7
  345. package/src/api/handlers/bylines.ts +5 -4
  346. package/src/api/handlers/content.ts +25 -7
  347. package/src/api/handlers/menus.ts +11 -6
  348. package/src/api/handlers/relations.ts +7 -2
  349. package/src/api/handlers/taxonomies.ts +22 -14
  350. package/src/api/schemas/bylines.ts +4 -4
  351. package/src/api/schemas/common.ts +7 -6
  352. package/src/api/schemas/menus.ts +2 -1
  353. package/src/api/schemas/relations.ts +3 -1
  354. package/src/api/schemas/taxonomies.ts +4 -2
  355. package/src/astro/prefetch.ts +7 -2
  356. package/src/astro/routes/api/admin/bylines/index.ts +5 -8
  357. package/src/astro/routes/api/oauth/authorize.ts +42 -10
  358. package/src/components/Gallery.astro +141 -26
  359. package/src/components/widgets/Categories.astro +1 -1
  360. package/src/components/widgets/Tags.astro +1 -1
  361. package/src/config/secrets.ts +25 -19
  362. package/src/content/converters/gallery.ts +61 -0
  363. package/src/content/converters/portable-text-to-prosemirror.ts +19 -0
  364. package/src/content/converters/prosemirror-to-portable-text.ts +18 -0
  365. package/src/content/converters/types.ts +36 -0
  366. package/src/database/dialect-helpers.ts +3 -4
  367. package/src/database/pg-migration-lock.ts +1 -4
  368. package/src/emdash-runtime.ts +55 -17
  369. package/src/i18n/config.ts +8 -0
  370. package/src/i18n/repair-locale-casing.ts +51 -0
  371. package/src/search/query.ts +3 -2
  372. package/src/seed/apply.ts +9 -7
  373. package/src/seed/validate.ts +15 -4
  374. package/src/taxonomies/index.ts +64 -23
  375. package/dist/api-b8WIiGU4.mjs.map +0 -1
  376. package/dist/apply-CmIJK9j8.mjs.map +0 -1
  377. package/dist/bylines-BJbT4gKS.mjs.map +0 -1
  378. package/dist/config-CVssduLe.mjs.map +0 -1
  379. package/dist/dialect-helpers-HxEQGl_1.mjs.map +0 -1
  380. package/dist/index-wYnC6u4o.d.mts.map +0 -1
  381. package/dist/media-kIV1IxFf.mjs.map +0 -1
  382. package/dist/menus-BcyElFtI.mjs.map +0 -1
  383. package/dist/menus-CZyG6rvx.mjs.map +0 -1
  384. package/dist/redirects-BBq3n2Rj.mjs.map +0 -1
  385. package/dist/relations-BKvO39-9.mjs.map +0 -1
  386. package/dist/relations-Bh8jlFsU.d.mts.map +0 -1
  387. package/dist/search-Bff-7jFt.mjs.map +0 -1
  388. package/dist/secrets-CSwQIl4q.mjs.map +0 -1
  389. package/dist/taxonomies-Ce49uIzY.mjs.map +0 -1
  390. package/dist/taxonomies-DjSKBZpq.mjs.map +0 -1
  391. package/dist/validate-V9nCwq_-.mjs.map +0 -1
  392. package/dist/version-907opKac.mjs +0 -7
@@ -2,11 +2,17 @@
2
2
  /**
3
3
  * Portable Text Gallery block component
4
4
  *
5
- * Renders image galleries from WordPress imports.
6
- * Uses Astro's Image component for optimization when dimensions are available.
5
+ * Renders image galleries from WordPress imports and EmDash media.
6
+ * Uses the provider's getEmbed/getSrc functions for external media (mirrors
7
+ * Image.astro), falling back to Astro's Image component when dimensions are
8
+ * available for locally-stored media.
7
9
  */
8
10
  import { Image as AstroImage } from "astro:assets";
11
+ import type { ImageEmbed } from "../media/types.js";
12
+ import { getMediaProvider } from "../media/provider-loader.js";
9
13
  import { buildRenderMediaUrl } from "../media/url.js";
14
+ import { toAbsoluteMediaUrl, RESPONSIVE_BREAKPOINTS } from "../media/responsive.js";
15
+ import { getPublicOrigin } from "../api/public-url.js";
10
16
 
11
17
  export interface Props {
12
18
  node: {
@@ -18,50 +24,159 @@ export interface Props {
18
24
  asset: {
19
25
  _ref: string;
20
26
  url?: string;
27
+ /** Provider ID for external media (e.g., "cloudflare-images") */
28
+ provider?: string;
21
29
  };
22
30
  alt?: string;
23
31
  caption?: string;
24
32
  width?: number;
25
33
  height?: number;
34
+ /** LQIP blurhash placeholder (images only) */
35
+ blurhash?: string;
36
+ /** LQIP dominant-color placeholder, as a CSS color (images only) */
37
+ dominantColor?: string;
26
38
  }>;
27
39
  columns?: number;
28
40
  };
41
+ /** Render the LQIP placeholder as an inline background. Disable for strict CSP. */
42
+ placeholder?: boolean;
29
43
  }
30
44
 
31
- const { node } = Astro.props;
45
+ /**
46
+ * Generate srcset using provider's getSrc function
47
+ */
48
+ function generateSrcset(
49
+ getSrc: NonNullable<ImageEmbed["getSrc"]>,
50
+ maxWidth: number,
51
+ aspectRatio?: number,
52
+ ): string {
53
+ return RESPONSIVE_BREAKPOINTS.filter((w) => w <= maxWidth * 2)
54
+ .map((w) => {
55
+ const h = aspectRatio ? Math.round(w / aspectRatio) : undefined;
56
+ return `${getSrc({ width: w, height: h })} ${w}w`;
57
+ })
58
+ .join(", ");
59
+ }
60
+
61
+ const { node, placeholder = true } = Astro.props;
32
62
  const images = node?.images ?? [];
33
63
  const columns = node?.columns ?? 3;
34
64
 
35
65
  if (!images.length) {
36
66
  return null;
37
67
  }
68
+
69
+ // Resolve provider embeds up front (async), keeping the template's `.map()`
70
+ // synchronous over the resolved data — mirrors Image.astro's per-image logic.
71
+ const resolvedImages = await Promise.all(
72
+ images.map(async (image) => {
73
+ const { asset, alt = "", width, height } = image;
74
+ const aspectRatio = width && height ? width / height : undefined;
75
+
76
+ let src = "";
77
+ let srcset: string | undefined;
78
+ let sizes: string | undefined;
79
+ let astroImageSrc = "";
80
+ const providerId = asset.provider;
81
+
82
+ if (providerId && providerId !== "local") {
83
+ const provider = await getMediaProvider(providerId);
84
+ if (provider) {
85
+ try {
86
+ const mediaValue = {
87
+ provider: providerId,
88
+ id: asset._ref,
89
+ width,
90
+ height,
91
+ alt,
92
+ };
93
+ const result = provider.getEmbed(mediaValue, { width, height });
94
+ const embed = result instanceof Promise ? await result : result;
95
+ if (embed.type === "image") {
96
+ src = embed.src;
97
+
98
+ if (embed.getSrc) {
99
+ const maxWidth = width || 1200;
100
+ srcset = generateSrcset(embed.getSrc, maxWidth, aspectRatio);
101
+ sizes = width ? `(min-width: ${width}px) ${width}px, 100vw` : "100vw";
102
+ }
103
+ }
104
+ } catch (error) {
105
+ console.warn(`Failed to get embed for image ${asset._ref}:`, error);
106
+ }
107
+ }
108
+ }
109
+
110
+ // Fallback for local provider. `asset.url` carries the storage key with
111
+ // extension when present; `asset._ref` is a bare ULID that only the
112
+ // internal `/file/{id}` route can resolve. `buildRenderMediaUrl` picks
113
+ // the right shape.
114
+ if (!src) {
115
+ src = buildRenderMediaUrl(Astro.locals.emdash?.getPublicMediaUrl, {
116
+ url: asset.url,
117
+ id: asset._ref,
118
+ });
119
+ if (width && height) {
120
+ astroImageSrc = toAbsoluteMediaUrl(
121
+ src,
122
+ getPublicOrigin(Astro.url, Astro.locals.emdash?.config),
123
+ );
124
+ }
125
+ }
126
+
127
+ // Prefer blurhash over dominant color for the LQIP placeholder, like
128
+ // Image.astro.
129
+ let placeholderStyle = "";
130
+ if (placeholder && image.blurhash) {
131
+ const { blurhashToImageCssString } = await import("@unpic/placeholder");
132
+ placeholderStyle = blurhashToImageCssString(image.blurhash);
133
+ } else if (placeholder && image.dominantColor) {
134
+ placeholderStyle = `background-color: ${image.dominantColor};`;
135
+ }
136
+
137
+ return {
138
+ key: image._key,
139
+ alt,
140
+ caption: image.caption,
141
+ width,
142
+ height,
143
+ src,
144
+ srcset,
145
+ sizes,
146
+ astroImageSrc,
147
+ placeholderStyle,
148
+ };
149
+ }),
150
+ );
38
151
  ---
39
152
 
40
153
  <div class="emdash-gallery" style={`--columns: ${columns}`}>
41
154
  {
42
- images.map((image) => {
43
- const src = buildRenderMediaUrl(Astro.locals.emdash?.getPublicMediaUrl, {
44
- url: image.asset.url,
45
- id: image.asset._ref,
46
- });
47
- const hasSize = image.width && image.height;
48
- return (
49
- <figure class="emdash-gallery-item">
50
- {hasSize ? (
51
- <AstroImage
52
- src={src}
53
- alt={image.alt || ""}
54
- width={image.width!}
55
- height={image.height!}
56
- layout="constrained"
57
- />
58
- ) : (
59
- <img src={src} alt={image.alt || ""} loading="lazy" decoding="async" />
60
- )}
61
- {image.caption && <figcaption>{image.caption}</figcaption>}
62
- </figure>
63
- );
64
- })
155
+ resolvedImages.map((image) => (
156
+ <figure class="emdash-gallery-item" style={image.placeholderStyle || undefined}>
157
+ {image.astroImageSrc ? (
158
+ <AstroImage
159
+ src={image.astroImageSrc}
160
+ alt={image.alt}
161
+ width={image.width!}
162
+ height={image.height!}
163
+ layout="constrained"
164
+ />
165
+ ) : (
166
+ <img
167
+ src={image.src}
168
+ srcset={image.srcset}
169
+ sizes={image.sizes}
170
+ alt={image.alt}
171
+ width={image.width}
172
+ height={image.height}
173
+ loading="lazy"
174
+ decoding="async"
175
+ />
176
+ )}
177
+ {image.caption && <figcaption>{image.caption}</figcaption>}
178
+ </figure>
179
+ ))
65
180
  }
66
181
  </div>
67
182
 
@@ -12,7 +12,7 @@ interface Props {
12
12
 
13
13
  const { showCount = true, hierarchical = true } = Astro.props;
14
14
 
15
- const categories = await getTaxonomyTerms("category");
15
+ const categories = await getTaxonomyTerms("category", { includeCounts: showCount });
16
16
  ---
17
17
 
18
18
  <ul class="widget-categories">
@@ -12,7 +12,7 @@ interface Props {
12
12
 
13
13
  const { showCount = false, limit = 20 } = Astro.props;
14
14
 
15
- const allTags = await getTaxonomyTerms("tag");
15
+ const allTags = await getTaxonomyTerms("tag", { includeCounts: showCount });
16
16
  const tags = allTags.slice(0, limit);
17
17
  ---
18
18
 
@@ -108,8 +108,9 @@ export interface ResolveSecretsOptions {
108
108
  db: Kysely<Database>;
109
109
  /**
110
110
  * Optional explicit env override map. When omitted, falls back to
111
- * `import.meta.env` via the global accessor below. Tests pass an
112
- * explicit map to avoid leaking process state.
111
+ * `process.env` via `readDefaultEnv` below (never `import.meta.env`
112
+ * see that function's docstring). Tests pass an explicit map to avoid
113
+ * leaking process state.
113
114
  */
114
115
  env?: SecretsEnv;
115
116
  /**
@@ -514,29 +515,34 @@ function decodeBase64urlStrict(input: string): Uint8Array | null {
514
515
  /**
515
516
  * Default env reader.
516
517
  *
517
- * Note: this is the **only** code path in core that reads both
518
- * `import.meta.env` and `process.env`. Route handlers should not — they
519
- * always run inside the Astro/Vite bundle where `import.meta.env` is
520
- * the correct source. This resolver is shared with the CLI surface (via
521
- * `cli/commands/secrets.ts`) which runs outside the bundle, so we
522
- * deliberately consult both. `import.meta.env` wins so build-time
523
- * substitutions are honored when present.
518
+ * Reads **only** `process.env`. This module must never reference
519
+ * `import.meta.env`: in an Astro/Vite production build the bare
520
+ * `import.meta.env` expression is statically replaced with an object
521
+ * literal built from the env loaded at build time, which (a) embeds any
522
+ * secret present in `.env` on the build machine into the shipped server
523
+ * bundle, and (b) makes that stale build-time value silently shadow the
524
+ * real runtime secret configured on the deployment platform (e.g. via
525
+ * `wrangler secret put`). See #2139.
526
+ *
527
+ * `process.env` is the runtime source on Node/container deployments and
528
+ * on Cloudflare Workers with Node compatibility (the same source
529
+ * `resolveS3Config` in `../storage/s3.ts` uses). The CLI surface
530
+ * (`cli/commands/secrets.ts`) runs outside the bundle where
531
+ * `process.env` is native.
524
532
  *
525
533
  * The convention documented in AGENTS.md ("import.meta.env.EMDASH_X ||
526
- * import.meta.env.X") is the route-handler convention; this is the
527
- * shared-with-CLI exception.
534
+ * import.meta.env.X") is for public, build-time config in route
535
+ * handlers; secrets are deliberately excluded from it.
528
536
  */
529
537
  function readDefaultEnv(): SecretsEnv {
530
- // eslint-disable-next-line typescript/no-unsafe-type-assertion -- import.meta.env is loose by design
531
- const meta = (import.meta.env ?? {}) as Record<string, string | undefined>;
532
538
  const proc = typeof process !== "undefined" && process.env ? process.env : {};
533
539
 
534
540
  return {
535
- EMDASH_ENCRYPTION_KEY: meta.EMDASH_ENCRYPTION_KEY ?? proc.EMDASH_ENCRYPTION_KEY,
536
- EMDASH_PREVIEW_SECRET: meta.EMDASH_PREVIEW_SECRET ?? proc.EMDASH_PREVIEW_SECRET,
537
- PREVIEW_SECRET: meta.PREVIEW_SECRET ?? proc.PREVIEW_SECRET,
538
- EMDASH_IP_SALT: meta.EMDASH_IP_SALT ?? proc.EMDASH_IP_SALT,
539
- EMDASH_AUTH_SECRET: meta.EMDASH_AUTH_SECRET ?? proc.EMDASH_AUTH_SECRET,
540
- AUTH_SECRET: meta.AUTH_SECRET ?? proc.AUTH_SECRET,
541
+ EMDASH_ENCRYPTION_KEY: proc.EMDASH_ENCRYPTION_KEY,
542
+ EMDASH_PREVIEW_SECRET: proc.EMDASH_PREVIEW_SECRET,
543
+ PREVIEW_SECRET: proc.PREVIEW_SECRET,
544
+ EMDASH_IP_SALT: proc.EMDASH_IP_SALT,
545
+ EMDASH_AUTH_SECRET: proc.EMDASH_AUTH_SECRET,
546
+ AUTH_SECRET: proc.AUTH_SECRET,
541
547
  };
542
548
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared sanitization for gallery block images, used by both converters so
3
+ * the editor round-trip and the stored shape stay in lockstep.
4
+ */
5
+
6
+ import type { PortableTextGalleryImage } from "./types.js";
7
+
8
+ /**
9
+ * Normalize an untrusted `images` value into well-formed gallery images.
10
+ * Non-object entries and entries without an asset object are dropped.
11
+ * Missing `_key`s are filled via `generateKey` when provided (PM → PT);
12
+ * left empty otherwise (PT → PM keeps whatever the block carried).
13
+ */
14
+ export function sanitizeGalleryImages(
15
+ value: unknown,
16
+ generateKey?: () => string,
17
+ ): PortableTextGalleryImage[] {
18
+ if (!Array.isArray(value)) return [];
19
+
20
+ const images: PortableTextGalleryImage[] = [];
21
+ for (const entry of value as unknown[]) {
22
+ if (!isRecord(entry)) continue;
23
+ const record = entry;
24
+ const asset = record.asset;
25
+ if (!isRecord(asset)) continue;
26
+ const assetRecord = asset;
27
+
28
+ const image: PortableTextGalleryImage = {
29
+ _type: "image",
30
+ _key:
31
+ typeof record._key === "string" && record._key
32
+ ? record._key
33
+ : generateKey
34
+ ? generateKey()
35
+ : "",
36
+ asset: {
37
+ _type: "reference",
38
+ _ref: typeof assetRecord._ref === "string" ? assetRecord._ref : "",
39
+ ...(typeof assetRecord.url === "string" && assetRecord.url ? { url: assetRecord.url } : {}),
40
+ ...(typeof assetRecord.provider === "string" && assetRecord.provider
41
+ ? { provider: assetRecord.provider }
42
+ : {}),
43
+ },
44
+ };
45
+ if (typeof record.alt === "string" && record.alt) image.alt = record.alt;
46
+ if (typeof record.caption === "string" && record.caption) image.caption = record.caption;
47
+ if (typeof record.width === "number") image.width = record.width;
48
+ if (typeof record.height === "number") image.height = record.height;
49
+ if (typeof record.blurhash === "string" && record.blurhash) image.blurhash = record.blurhash;
50
+ if (typeof record.dominantColor === "string" && record.dominantColor)
51
+ image.dominantColor = record.dominantColor;
52
+
53
+ images.push(image);
54
+ }
55
+
56
+ return images;
57
+ }
58
+
59
+ function isRecord(value: unknown): value is Record<string, unknown> {
60
+ return typeof value === "object" && value !== null && !Array.isArray(value);
61
+ }
@@ -4,6 +4,7 @@
4
4
  * Converts Portable Text to TipTap's ProseMirror JSON format for editing.
5
5
  */
6
6
 
7
+ import { sanitizeGalleryImages } from "./gallery.js";
7
8
  import type {
8
9
  ProseMirrorDocument,
9
10
  ProseMirrorNode,
@@ -13,6 +14,7 @@ import type {
13
14
  PortableTextSpan,
14
15
  PortableTextMarkDef,
15
16
  PortableTextImageBlock,
17
+ PortableTextGalleryBlock,
16
18
  PortableTextCodeBlock,
17
19
  } from "./types.js";
18
20
 
@@ -128,6 +130,14 @@ function isImageBlock(block: PortableTextBlock): block is PortableTextImageBlock
128
130
  );
129
131
  }
130
132
 
133
+ /**
134
+ * Type guard for gallery blocks. Requires an `images` array — a gallery
135
+ * without one is malformed and falls through to the unknown-block path.
136
+ */
137
+ function isGalleryBlock(block: PortableTextBlock): block is PortableTextGalleryBlock {
138
+ return block._type === "gallery" && "images" in block && Array.isArray(block.images);
139
+ }
140
+
131
141
  /**
132
142
  * Type guard for code blocks
133
143
  */
@@ -149,6 +159,15 @@ function convertBlock(block: PortableTextBlock): ProseMirrorNode | null {
149
159
  // Malformed image block (no asset wrapper) — extract url from top level
150
160
  return convertMalformedImage(block);
151
161
  }
162
+ if (isGalleryBlock(block)) {
163
+ return {
164
+ type: "gallery",
165
+ attrs: {
166
+ images: sanitizeGalleryImages(block.images),
167
+ columns: typeof block.columns === "number" ? block.columns : undefined,
168
+ },
169
+ };
170
+ }
152
171
  if (isCodeBlock(block)) {
153
172
  return convertCodeBlock(block);
154
173
  }
@@ -4,6 +4,7 @@
4
4
  * Converts TipTap's ProseMirror JSON format to Portable Text for storage.
5
5
  */
6
6
 
7
+ import { sanitizeGalleryImages } from "./gallery.js";
7
8
  import type {
8
9
  ProseMirrorDocument,
9
10
  ProseMirrorNode,
@@ -13,6 +14,7 @@ import type {
13
14
  PortableTextSpan,
14
15
  PortableTextMarkDef,
15
16
  PortableTextImageBlock,
17
+ PortableTextGalleryBlock,
16
18
  PortableTextCodeBlock,
17
19
  PortableTextHtmlBlock,
18
20
  } from "./types.js";
@@ -77,6 +79,9 @@ function convertNode(node: ProseMirrorNode): PortableTextBlock | PortableTextBlo
77
79
  case "image":
78
80
  return convertImage(node);
79
81
 
82
+ case "gallery":
83
+ return convertGallery(node);
84
+
80
85
  case "horizontalRule":
81
86
  return {
82
87
  _type: "break",
@@ -327,6 +332,19 @@ function convertImage(node: ProseMirrorNode): PortableTextImageBlock {
327
332
  };
328
333
  }
329
334
 
335
+ /**
336
+ * Convert gallery node to Portable Text
337
+ */
338
+ function convertGallery(node: ProseMirrorNode): PortableTextGalleryBlock {
339
+ const columns = node.attrs?.columns;
340
+ return {
341
+ _type: "gallery",
342
+ _key: generateKey(),
343
+ images: sanitizeGalleryImages(node.attrs?.images, generateKey),
344
+ ...(typeof columns === "number" ? { columns } : {}),
345
+ };
346
+ }
347
+
330
348
  /**
331
349
  * Convert inline content (text nodes with marks) to Portable Text spans
332
350
  */
@@ -70,6 +70,41 @@ export interface PortableTextImageBlock {
70
70
  displayHeight?: number;
71
71
  }
72
72
 
73
+ /**
74
+ * A single image inside a gallery block. Mirrors the shape produced by
75
+ * gutenberg-to-portable-text and consumed by Gallery.astro.
76
+ */
77
+ export interface PortableTextGalleryImage {
78
+ _type: "image";
79
+ _key: string;
80
+ asset: {
81
+ /** Present on WordPress-imported galleries; always emitted on round-trip */
82
+ _type?: "reference";
83
+ _ref: string;
84
+ url?: string;
85
+ /** Provider ID for external media (e.g., "cloudflare-images") */
86
+ provider?: string;
87
+ };
88
+ alt?: string;
89
+ caption?: string;
90
+ width?: number;
91
+ height?: number;
92
+ /** LQIP blurhash placeholder (images only) */
93
+ blurhash?: string;
94
+ /** LQIP dominant-color placeholder, as a CSS color (images only) */
95
+ dominantColor?: string;
96
+ }
97
+
98
+ /**
99
+ * Gallery block (grid of images with optional per-image captions)
100
+ */
101
+ export interface PortableTextGalleryBlock {
102
+ _type: "gallery";
103
+ _key: string;
104
+ images: PortableTextGalleryImage[];
105
+ columns?: number;
106
+ }
107
+
73
108
  /**
74
109
  * Code block
75
110
  */
@@ -105,6 +140,7 @@ export interface PortableTextUnknownBlock {
105
140
  export type PortableTextBlock =
106
141
  | PortableTextTextBlock
107
142
  | PortableTextImageBlock
143
+ | PortableTextGalleryBlock
108
144
  | PortableTextCodeBlock
109
145
  | PortableTextHtmlBlock
110
146
  | PortableTextUnknownBlock;
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import type { ColumnDataType, Kysely, RawBuilder } from "kysely";
14
- import { sql } from "kysely";
14
+ import { PostgresAdapter, sql } from "kysely";
15
15
 
16
16
  import type { DatabaseDialectType } from "../db/adapters.js";
17
17
  import { validateIdentifier, validateJsonFieldName } from "./validate.js";
@@ -19,12 +19,11 @@ import { validateIdentifier, validateJsonFieldName } from "./validate.js";
19
19
  export type { DatabaseDialectType };
20
20
 
21
21
  /**
22
- * Detect dialect type from a Kysely instance via the adapter class name.
22
+ * Detect dialect type from a Kysely instance via the adapter class.
23
23
  */
24
24
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance
25
25
  export function detectDialect(db: Kysely<any>): DatabaseDialectType {
26
- const name = db.getExecutor().adapter.constructor.name;
27
- if (name === "PostgresAdapter") return "postgres";
26
+ if (db.getExecutor().adapter instanceof PostgresAdapter) return "postgres";
28
27
  return "sqlite";
29
28
  }
30
29
 
@@ -38,10 +38,7 @@ const KYSELY_PG_MIGRATION_LOCK_ID = BigInt("3853314791062309107");
38
38
 
39
39
  /**
40
40
  * Extends the stock adapter so every capability flag and `instanceof` check
41
- * is inherited, and keeps the class NAME `PostgresAdapter` because
42
- * `detectDialect()` (dialect-helpers.ts) identifies the dialect via
43
- * `adapter.constructor.name` — a differently-named adapter would make every
44
- * dialect helper fall back to SQLite SQL against a Postgres database.
41
+ * is inherited.
45
42
  */
46
43
  class PostgresAdapter extends KyselyPostgresAdapter {
47
44
  // eslint-disable-next-line typescript/no-explicit-any -- matches the DialectAdapter signature
@@ -36,6 +36,8 @@ import type {
36
36
  ContentDateField,
37
37
  } from "./database/repositories/types.js";
38
38
  import { validateIdentifier } from "./database/validate.js";
39
+ import { getI18nConfig } from "./i18n/config.js";
40
+ import { repairLocaleCasing } from "./i18n/repair-locale-casing.js";
39
41
  import { normalizeMediaValue } from "./media/normalize.js";
40
42
  import type { MediaProvider, MediaProviderCapabilities } from "./media/types.js";
41
43
  import {
@@ -71,6 +73,12 @@ import { createSingleFlightCache, singleFlightCached } from "./utils/single-flig
71
73
  import { COMMIT, VERSION } from "./version.js";
72
74
 
73
75
  const LEADING_SLASH_PATTERN = /^\//;
76
+ const LOCALE_CASING_REPAIR_OPTION = "emdash:repair_locale_casing";
77
+
78
+ function getLocaleCasingRepairVersion(locales: readonly string[]): string | null {
79
+ if (locales.length === 0) return null;
80
+ return `1:${locales.toSorted().join(",")}`;
81
+ }
74
82
 
75
83
  /**
76
84
  * Parse a JSON column expected to contain an array of strings.
@@ -1130,6 +1138,10 @@ export class EmDashRuntime {
1130
1138
  const storage = EmDashRuntime.getStorage(deps);
1131
1139
 
1132
1140
  let pluginStates: Map<string, string> = new Map();
1141
+ const configuredLocales: string[] =
1142
+ virtualConfig?.i18n?.locales ?? getI18nConfig()?.locales ?? [];
1143
+ const localeCasingRepairVersion = getLocaleCasingRepairVersion(configuredLocales);
1144
+ let storedLocaleCasingRepairVersion: string | undefined;
1133
1145
  let siteInfo:
1134
1146
  | {
1135
1147
  siteName?: string;
@@ -1175,7 +1187,9 @@ export class EmDashRuntime {
1175
1187
  "emdash:site_title",
1176
1188
  "emdash:site_url",
1177
1189
  "emdash:locale",
1190
+ LOCALE_CASING_REPAIR_OPTION,
1178
1191
  ]);
1192
+ storedLocaleCasingRepairVersion = siteOpts.get(LOCALE_CASING_REPAIR_OPTION);
1179
1193
  return {
1180
1194
  siteName: siteOpts.get("emdash:site_title") ?? undefined,
1181
1195
  siteUrl: siteOpts.get("emdash:site_url") ?? undefined,
@@ -1241,6 +1255,20 @@ export class EmDashRuntime {
1241
1255
 
1242
1256
  await Promise.all(coldStartReads);
1243
1257
 
1258
+ if (
1259
+ localeCasingRepairVersion &&
1260
+ (configuredLocales.some(
1261
+ (locale) => locale.includes("-") || locale !== locale.toLowerCase(),
1262
+ ) ||
1263
+ storedLocaleCasingRepairVersion !== undefined) &&
1264
+ storedLocaleCasingRepairVersion !== localeCasingRepairVersion
1265
+ ) {
1266
+ await phase("rt.locale", "Repair locale casing", async () => {
1267
+ await repairLocaleCasing(db, configuredLocales);
1268
+ await new OptionsRepository(db).set(LOCALE_CASING_REPAIR_OPTION, localeCasingRepairVersion);
1269
+ });
1270
+ }
1271
+
1244
1272
  // Auto-seed the default schema for a first load that skipped the setup
1245
1273
  // wizard (the wizard and dev-bypass apply seeds explicitly). Run under a
1246
1274
  // per-isolate lock keyed by the configured db so a reclaimed-and-rerun
@@ -2965,7 +2993,7 @@ export class EmDashRuntime {
2965
2993
  await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.id]);
2966
2994
  }
2967
2995
 
2968
- // Run afterDelete hooks (fire-and-forget)
2996
+ // Run afterDelete hooks (deferred past the response via after())
2969
2997
  if (result.success) {
2970
2998
  this.runAfterDeleteHooks(id, collection, false);
2971
2999
  }
@@ -3052,7 +3080,7 @@ export class EmDashRuntime {
3052
3080
  await this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);
3053
3081
  }
3054
3082
 
3055
- // Run afterUnpublish hooks (fire-and-forget)
3083
+ // Run afterUnpublish hooks (deferred past the response via after())
3056
3084
  if (result.success && result.data) {
3057
3085
  this.runAfterUnpublishHooks(contentItemToRecord(result.data.item), collection);
3058
3086
  }
@@ -3746,24 +3774,34 @@ export class EmDashRuntime {
3746
3774
  }
3747
3775
 
3748
3776
  private runAfterDeleteHooks(id: string, collection: string, permanent: boolean): void {
3749
- // Trusted plugins
3750
- if (this.hooks.hasHooks("content:afterDelete")) {
3751
- this.hooks
3752
- .runContentAfterDelete(id, collection, permanent)
3753
- .catch((err) => console.error("EmDash afterDelete hook error:", err));
3754
- }
3777
+ after(async () => {
3778
+ // Trusted plugins
3779
+ if (this.hooks.hasHooks("content:afterDelete")) {
3780
+ try {
3781
+ await this.hooks.runContentAfterDelete(id, collection, permanent);
3782
+ } catch (err) {
3783
+ console.error("EmDash afterDelete hook error:", err);
3784
+ }
3785
+ }
3755
3786
 
3756
- // Sandboxed plugins
3757
- for (const [pluginKey, plugin] of this.sandboxedPlugins) {
3758
- const [pluginId] = pluginKey.split(":");
3759
- if (!pluginId || !this.isPluginEnabled(pluginId)) continue;
3787
+ // Sandboxed plugins
3788
+ const tasks: Promise<void>[] = [];
3789
+ for (const [pluginKey, plugin] of this.sandboxedPlugins) {
3790
+ const [pluginId] = pluginKey.split(":");
3791
+ if (!pluginId || !this.isPluginEnabled(pluginId)) continue;
3760
3792
 
3761
- plugin
3762
- .invokeHook("content:afterDelete", { id, collection, permanent })
3763
- .catch((err) =>
3764
- console.error(`EmDash: Sandboxed plugin ${pluginId} afterDelete error:`, err),
3793
+ tasks.push(
3794
+ (async () => {
3795
+ try {
3796
+ await plugin.invokeHook("content:afterDelete", { id, collection, permanent });
3797
+ } catch (err) {
3798
+ console.error(`EmDash: Sandboxed plugin ${pluginId} afterDelete error:`, err);
3799
+ }
3800
+ })(),
3765
3801
  );
3766
- }
3802
+ }
3803
+ await Promise.allSettled(tasks);
3804
+ });
3767
3805
  }
3768
3806
 
3769
3807
  private runDeferredContentHook(
@@ -30,6 +30,14 @@ export function getI18nConfig(): I18nConfig | null {
30
30
  return _config ?? null;
31
31
  }
32
32
 
33
+ /** Match a locale to the exact casing used by the site configuration. */
34
+ export function resolveConfiguredLocale(locale: string): string {
35
+ return (
36
+ _config?.locales.find((configured) => configured.toLowerCase() === locale.toLowerCase()) ??
37
+ locale
38
+ );
39
+ }
40
+
33
41
  /**
34
42
  * Check if i18n is enabled.
35
43
  * Returns true when multiple locales are configured.