emdash 0.28.1 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (680) hide show
  1. package/dist/{adapters-hUy8vOB-.d.mts → adapters-DPAkL6QF.d.mts} +1 -1
  2. package/dist/{adapters-hUy8vOB-.d.mts.map → adapters-DPAkL6QF.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-CRsJ6BPM.mjs → allowed-origins-FXB-FplB.mjs} +2 -2
  4. package/dist/{allowed-origins-CRsJ6BPM.mjs.map → allowed-origins-FXB-FplB.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +22 -21
  7. package/dist/api/route-utils.mjs.map +1 -1
  8. package/dist/api/schemas/index.d.mts +2 -2
  9. package/dist/api/schemas/index.mjs +4 -4
  10. package/dist/{api-DxFSorgk.mjs → api-DVbHNApo.mjs} +75 -23
  11. package/dist/api-DVbHNApo.mjs.map +1 -0
  12. package/dist/{api-tokens-DvqeZmVq.mjs → api-tokens-UYBkl17d.mjs} +2 -2
  13. package/dist/{api-tokens-DvqeZmVq.mjs.map → api-tokens-UYBkl17d.mjs.map} +1 -1
  14. package/dist/{apply-CDJqIjPU.mjs → apply-D23iUV4q.mjs} +20 -20
  15. package/dist/{apply-CDJqIjPU.mjs.map → apply-D23iUV4q.mjs.map} +1 -1
  16. package/dist/astro/index.d.mts +13 -12
  17. package/dist/astro/index.d.mts.map +1 -1
  18. package/dist/astro/index.mjs +10 -5
  19. package/dist/astro/index.mjs.map +1 -1
  20. package/dist/astro/middleware/auth.d.mts +11 -10
  21. package/dist/astro/middleware/auth.d.mts.map +1 -1
  22. package/dist/astro/middleware/auth.mjs +8 -8
  23. package/dist/astro/middleware/redirect.mjs +7 -7
  24. package/dist/astro/middleware/request-context.d.mts +3 -0
  25. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  26. package/dist/astro/middleware/request-context.mjs +257 -30
  27. package/dist/astro/middleware/request-context.mjs.map +1 -1
  28. package/dist/astro/middleware/setup.mjs +1 -1
  29. package/dist/astro/middleware.d.mts +2 -2
  30. package/dist/astro/middleware.d.mts.map +1 -1
  31. package/dist/astro/middleware.mjs +125 -79
  32. package/dist/astro/middleware.mjs.map +1 -1
  33. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +7 -7
  34. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +7 -7
  35. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +5 -5
  36. package/dist/astro/routes/api/admin/api-tokens/index.mjs +6 -6
  37. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +6 -6
  38. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +10 -10
  39. package/dist/astro/routes/api/admin/byline-fields/index.mjs +10 -10
  40. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +10 -10
  41. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +17 -17
  42. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +17 -17
  43. package/dist/astro/routes/api/admin/bylines/index.mjs +17 -17
  44. package/dist/astro/routes/api/admin/comments/_id_/status.d.mts.map +1 -1
  45. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +13 -13
  46. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  47. package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
  48. package/dist/astro/routes/api/admin/comments/bulk.mjs +10 -10
  49. package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
  50. package/dist/astro/routes/api/admin/comments/index.d.mts.map +1 -1
  51. package/dist/astro/routes/api/admin/comments/index.mjs +10 -10
  52. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  53. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +6 -6
  54. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +5 -5
  55. package/dist/astro/routes/api/admin/media-usage/repair.d.mts +8 -0
  56. package/dist/astro/routes/api/admin/media-usage/repair.d.mts.map +1 -0
  57. package/dist/astro/routes/api/admin/media-usage/repair.mjs +28 -0
  58. package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -0
  59. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +5 -5
  60. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +5 -5
  61. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +43 -41
  62. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  63. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +43 -41
  64. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  65. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +42 -40
  66. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  67. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +42 -40
  68. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  69. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +42 -40
  70. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  71. package/dist/astro/routes/api/admin/plugins/index.mjs +42 -40
  72. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  73. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +4 -4
  74. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +42 -40
  75. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  76. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +42 -40
  77. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  78. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +42 -40
  79. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  80. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +42 -40
  81. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  82. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +43 -41
  83. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  84. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +42 -40
  85. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  86. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +43 -41
  87. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  88. package/dist/astro/routes/api/admin/plugins/updates.mjs +42 -40
  89. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  90. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +42 -40
  91. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  92. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +4 -4
  93. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +42 -40
  94. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  95. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +4 -4
  96. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +3 -3
  97. package/dist/astro/routes/api/admin/users/_id_/index.mjs +8 -8
  98. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +5 -5
  99. package/dist/astro/routes/api/admin/users/index.mjs +7 -7
  100. package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -7
  101. package/dist/astro/routes/api/auth/invite/accept.mjs +3 -3
  102. package/dist/astro/routes/api/auth/invite/complete.mjs +12 -12
  103. package/dist/astro/routes/api/auth/invite/index.mjs +9 -9
  104. package/dist/astro/routes/api/auth/invite/register-options.mjs +11 -11
  105. package/dist/astro/routes/api/auth/logout.mjs +4 -4
  106. package/dist/astro/routes/api/auth/magic-link/send.mjs +11 -11
  107. package/dist/astro/routes/api/auth/magic-link/verify.mjs +4 -4
  108. package/dist/astro/routes/api/auth/me.mjs +8 -8
  109. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  110. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +4 -4
  111. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs.map +1 -1
  112. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  113. package/dist/astro/routes/api/auth/oauth/_provider_.mjs.map +1 -1
  114. package/dist/astro/routes/api/auth/passkey/_id_.mjs +7 -7
  115. package/dist/astro/routes/api/auth/passkey/index.mjs +3 -3
  116. package/dist/astro/routes/api/auth/passkey/options.mjs +13 -13
  117. package/dist/astro/routes/api/auth/passkey/register/options.mjs +11 -11
  118. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +12 -12
  119. package/dist/astro/routes/api/auth/passkey/verify.mjs +12 -12
  120. package/dist/astro/routes/api/auth/signup/complete.mjs +12 -12
  121. package/dist/astro/routes/api/auth/signup/request.mjs +11 -11
  122. package/dist/astro/routes/api/auth/signup/verify.mjs +3 -3
  123. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.d.mts.map +1 -1
  124. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +69 -14
  125. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  126. package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +13 -13
  127. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +4 -4
  128. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +4 -4
  129. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +4 -4
  130. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +4 -4
  131. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +12 -12
  132. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +8 -8
  133. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts +9 -0
  134. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.d.mts.map +1 -0
  135. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +71 -0
  136. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -0
  137. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.d.mts +8 -0
  138. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.d.mts.map +1 -0
  139. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +50 -0
  140. package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -0
  141. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +4 -4
  142. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +4 -4
  143. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +8 -8
  144. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +15 -15
  145. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +4 -4
  146. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +4 -4
  147. package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
  148. package/dist/astro/routes/api/content/_collection_/authors.mjs +4 -4
  149. package/dist/astro/routes/api/content/_collection_/index.mjs +8 -8
  150. package/dist/astro/routes/api/content/_collection_/trash.mjs +8 -8
  151. package/dist/astro/routes/api/dashboard.mjs +8 -8
  152. package/dist/astro/routes/api/dev/emails.mjs +4 -4
  153. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  154. package/dist/astro/routes/api/import/probe.mjs +18 -18
  155. package/dist/astro/routes/api/import/wordpress/analyze.mjs +5 -5
  156. package/dist/astro/routes/api/import/wordpress/execute.d.mts +12 -11
  157. package/dist/astro/routes/api/import/wordpress/execute.d.mts.map +1 -1
  158. package/dist/astro/routes/api/import/wordpress/execute.mjs +16 -16
  159. package/dist/astro/routes/api/import/wordpress/media.d.mts +11 -10
  160. package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
  161. package/dist/astro/routes/api/import/wordpress/media.mjs +11 -11
  162. package/dist/astro/routes/api/import/wordpress/prepare.mjs +11 -11
  163. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +12 -16
  164. package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts.map +1 -1
  165. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +11 -11
  166. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  167. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +18 -18
  168. package/dist/astro/routes/api/import/wordpress-plugin/callback.mjs +1 -1
  169. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +12 -11
  170. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts.map +1 -1
  171. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +28 -28
  172. package/dist/astro/routes/api/manifest.mjs +6 -6
  173. package/dist/astro/routes/api/mcp.mjs +56 -47
  174. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  175. package/dist/astro/routes/api/media/_id_/confirm.mjs +9 -9
  176. package/dist/astro/routes/api/media/_id_.mjs +8 -8
  177. package/dist/astro/routes/api/media/file/_...key_.mjs +3 -3
  178. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +4 -4
  179. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +4 -4
  180. package/dist/astro/routes/api/media/providers/index.mjs +4 -4
  181. package/dist/astro/routes/api/media/upload-url.mjs +10 -10
  182. package/dist/astro/routes/api/media.mjs +13 -13
  183. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +9 -9
  184. package/dist/astro/routes/api/menus/_name_/items.mjs +9 -9
  185. package/dist/astro/routes/api/menus/_name_/reorder.mjs +9 -9
  186. package/dist/astro/routes/api/menus/_name_/translations.mjs +9 -9
  187. package/dist/astro/routes/api/menus/_name_.mjs +9 -9
  188. package/dist/astro/routes/api/menus/index.mjs +9 -9
  189. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  190. package/dist/astro/routes/api/oauth/device/authorize.mjs +7 -7
  191. package/dist/astro/routes/api/oauth/device/code.mjs +10 -10
  192. package/dist/astro/routes/api/oauth/device/token.mjs +9 -9
  193. package/dist/astro/routes/api/oauth/register.mjs +4 -4
  194. package/dist/astro/routes/api/oauth/token/refresh.mjs +7 -7
  195. package/dist/astro/routes/api/oauth/token/revoke.mjs +7 -7
  196. package/dist/astro/routes/api/oauth/token.mjs +7 -7
  197. package/dist/astro/routes/api/openapi.json.mjs +24 -5
  198. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  199. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +5 -5
  200. package/dist/astro/routes/api/redirects/404s/index.mjs +12 -12
  201. package/dist/astro/routes/api/redirects/404s/summary.mjs +12 -12
  202. package/dist/astro/routes/api/redirects/_id_.mjs +13 -13
  203. package/dist/astro/routes/api/redirects/index.mjs +13 -13
  204. package/dist/astro/routes/api/relations/_id_/index.d.mts +10 -0
  205. package/dist/astro/routes/api/relations/_id_/index.d.mts.map +1 -0
  206. package/dist/astro/routes/api/relations/_id_/index.mjs +69 -0
  207. package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -0
  208. package/dist/astro/routes/api/relations/_id_/translations.d.mts +8 -0
  209. package/dist/astro/routes/api/relations/_id_/translations.d.mts.map +1 -0
  210. package/dist/astro/routes/api/relations/_id_/translations.mjs +34 -0
  211. package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -0
  212. package/dist/astro/routes/api/relations/index.d.mts +9 -0
  213. package/dist/astro/routes/api/relations/index.d.mts.map +1 -0
  214. package/dist/astro/routes/api/relations/index.mjs +53 -0
  215. package/dist/astro/routes/api/relations/index.mjs.map +1 -0
  216. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +4 -4
  217. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +4 -4
  218. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.d.mts.map +1 -1
  219. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +42 -40
  220. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  221. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.d.mts.map +1 -1
  222. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +42 -40
  223. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  224. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +42 -40
  225. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  226. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +42 -40
  227. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  228. package/dist/astro/routes/api/schema/collections/index.mjs +42 -40
  229. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  230. package/dist/astro/routes/api/schema/index.mjs +9 -9
  231. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +42 -40
  232. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  233. package/dist/astro/routes/api/schema/orphans/index.mjs +42 -40
  234. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  235. package/dist/astro/routes/api/search/enable.mjs +12 -12
  236. package/dist/astro/routes/api/search/index.mjs +13 -12
  237. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  238. package/dist/astro/routes/api/search/rebuild.mjs +12 -12
  239. package/dist/astro/routes/api/search/stats.mjs +8 -8
  240. package/dist/astro/routes/api/search/suggest.mjs +11 -11
  241. package/dist/astro/routes/api/sections/_slug_.mjs +11 -11
  242. package/dist/astro/routes/api/sections/index.mjs +11 -11
  243. package/dist/astro/routes/api/settings/email.mjs +6 -6
  244. package/dist/astro/routes/api/settings.mjs +15 -15
  245. package/dist/astro/routes/api/setup/admin-verify.mjs +13 -13
  246. package/dist/astro/routes/api/setup/admin.mjs +12 -12
  247. package/dist/astro/routes/api/setup/dev-bypass.mjs +29 -29
  248. package/dist/astro/routes/api/setup/dev-reset.mjs +4 -4
  249. package/dist/astro/routes/api/setup/index.mjs +30 -30
  250. package/dist/astro/routes/api/setup/status.mjs +5 -5
  251. package/dist/astro/routes/api/snapshot.mjs +8 -8
  252. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +15 -15
  253. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +15 -15
  254. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +15 -15
  255. package/dist/astro/routes/api/taxonomies/index.mjs +15 -15
  256. package/dist/astro/routes/api/themes/preview.mjs +7 -7
  257. package/dist/astro/routes/api/typegen.mjs +6 -6
  258. package/dist/astro/routes/api/well-known/auth.mjs +2 -2
  259. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  260. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  261. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +8 -8
  262. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +12 -12
  263. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +12 -12
  264. package/dist/astro/routes/api/widget-areas/_name_.mjs +8 -8
  265. package/dist/astro/routes/api/widget-areas/index.mjs +12 -12
  266. package/dist/astro/routes/api/widget-components.mjs +4 -4
  267. package/dist/astro/routes/robots.txt.mjs +9 -9
  268. package/dist/astro/routes/sitemap-_collection_.xml.mjs +12 -12
  269. package/dist/astro/routes/sitemap.xml.mjs +10 -10
  270. package/dist/astro/types.d.mts +16 -13
  271. package/dist/astro/types.d.mts.map +1 -1
  272. package/dist/auth/providers/github.d.mts +1 -1
  273. package/dist/auth/providers/google.d.mts +1 -1
  274. package/dist/{authorize-Dz88F_Fw.mjs → authorize-BmCwQCib.mjs} +2 -2
  275. package/dist/{authorize-Dz88F_Fw.mjs.map → authorize-BmCwQCib.mjs.map} +1 -1
  276. package/dist/{base64-CmWvODNW.mjs → base64-C74Msx8h.mjs} +1 -1
  277. package/dist/{base64-CmWvODNW.mjs.map → base64-C74Msx8h.mjs.map} +1 -1
  278. package/dist/{byline-Bm8ZgUP9.mjs → byline-BWb5H-65.mjs} +8 -8
  279. package/dist/{byline-Bm8ZgUP9.mjs.map → byline-BWb5H-65.mjs.map} +1 -1
  280. package/dist/{byline-fields-CIx_GuN4.mjs → byline-fields-DO6bAYK8.mjs} +2 -2
  281. package/dist/{byline-fields-CIx_GuN4.mjs.map → byline-fields-DO6bAYK8.mjs.map} +1 -1
  282. package/dist/{byline-registry-BOjqDOim.mjs → byline-registry-BCuOp4UF.mjs} +3 -3
  283. package/dist/{byline-registry-BOjqDOim.mjs.map → byline-registry-BCuOp4UF.mjs.map} +1 -1
  284. package/dist/{bylines-6tm9mgQb.mjs → bylines-BGuKFaBy.mjs} +4 -4
  285. package/dist/{bylines-6tm9mgQb.mjs.map → bylines-BGuKFaBy.mjs.map} +1 -1
  286. package/dist/{bylines-CAZxGSrm.mjs → bylines-CHojDMpj.mjs} +8 -8
  287. package/dist/{bylines-CAZxGSrm.mjs.map → bylines-CHojDMpj.mjs.map} +1 -1
  288. package/dist/{cache-CEBAZjir.mjs → cache-BeR7RfN8.mjs} +3 -3
  289. package/dist/{cache-CEBAZjir.mjs.map → cache-BeR7RfN8.mjs.map} +1 -1
  290. package/dist/{challenge-store-anhwVER0.mjs → challenge-store-B426wQ6O.mjs} +1 -1
  291. package/dist/{challenge-store-anhwVER0.mjs.map → challenge-store-B426wQ6O.mjs.map} +1 -1
  292. package/dist/{chunks-B2isWQhr.mjs → chunks-BU5_nrAC.mjs} +2 -2
  293. package/dist/{chunks-B2isWQhr.mjs.map → chunks-BU5_nrAC.mjs.map} +1 -1
  294. package/dist/cli/index.mjs +28 -28
  295. package/dist/cli/index.mjs.map +1 -1
  296. package/dist/client/cf-access.d.mts +1 -1
  297. package/dist/client/index.d.mts +34 -2
  298. package/dist/client/index.d.mts.map +1 -1
  299. package/dist/client/index.mjs +6 -2
  300. package/dist/client/index.mjs.map +1 -1
  301. package/dist/{comment-CzeatwBV.mjs → comment-SWh31kS0.mjs} +2 -2
  302. package/dist/{comment-CzeatwBV.mjs.map → comment-SWh31kS0.mjs.map} +1 -1
  303. package/dist/{comment-reaction-Cj9QJY7I.mjs → comment-reaction-DgcpziXI.mjs} +2 -2
  304. package/dist/{comment-reaction-Cj9QJY7I.mjs.map → comment-reaction-DgcpziXI.mjs.map} +1 -1
  305. package/dist/{comments-BLE-jv5k.mjs → comments-kZYGppMS.mjs} +3 -3
  306. package/dist/{comments-BLE-jv5k.mjs.map → comments-kZYGppMS.mjs.map} +1 -1
  307. package/dist/{components-B_sATNUc.mjs → components-CliOkZAs.mjs} +1 -1
  308. package/dist/{components-B_sATNUc.mjs.map → components-CliOkZAs.mjs.map} +1 -1
  309. package/dist/{content-DhgEth4k.mjs → content-BO61Sx4j.mjs} +179 -20
  310. package/dist/content-BO61Sx4j.mjs.map +1 -0
  311. package/dist/{content-refresh-Br4gMzh8.mjs → content-refresh-BkkApzeS.mjs} +8 -8
  312. package/dist/{content-refresh-Br4gMzh8.mjs.map → content-refresh-BkkApzeS.mjs.map} +1 -1
  313. package/dist/{context-BRIpHARC.mjs → context-DtSFYFuS.mjs} +71 -14
  314. package/dist/context-DtSFYFuS.mjs.map +1 -0
  315. package/dist/{cron-v4FTEFR_.mjs → cron-YFJ7uNVA.mjs} +1 -1
  316. package/dist/{cron-v4FTEFR_.mjs.map → cron-YFJ7uNVA.mjs.map} +1 -1
  317. package/dist/{dashboard-EvEKF5en.mjs → dashboard-xo_gPs_D.mjs} +6 -5
  318. package/dist/dashboard-xo_gPs_D.mjs.map +1 -0
  319. package/dist/database/pg-migration-lock.d.mts +21 -0
  320. package/dist/database/pg-migration-lock.d.mts.map +1 -0
  321. package/dist/database/pg-migration-lock.mjs +45 -0
  322. package/dist/database/pg-migration-lock.mjs.map +1 -0
  323. package/dist/db/index.d.mts +3 -3
  324. package/dist/db/index.mjs +2 -2
  325. package/dist/db/libsql.d.mts +1 -1
  326. package/dist/db/postgres.d.mts +1 -1
  327. package/dist/db/postgres.d.mts.map +1 -1
  328. package/dist/db/postgres.mjs +2 -8
  329. package/dist/db/postgres.mjs.map +1 -1
  330. package/dist/db/sqlite.d.mts +1 -1
  331. package/dist/{db-errors-CK46D-ly.mjs → db-errors-BKuXh_fi.mjs} +1 -1
  332. package/dist/{db-errors-CK46D-ly.mjs.map → db-errors-BKuXh_fi.mjs.map} +1 -1
  333. package/dist/{default-CM_D83Mg.mjs → default-DY5ovGUG.mjs} +1 -1
  334. package/dist/{default-CM_D83Mg.mjs.map → default-DY5ovGUG.mjs.map} +1 -1
  335. package/dist/{device-flow-CfGP18Uw.mjs → device-flow-CPXQ_QfW.mjs} +5 -5
  336. package/dist/{device-flow-CfGP18Uw.mjs.map → device-flow-CPXQ_QfW.mjs.map} +1 -1
  337. package/dist/{dialect-helpers-DRI5pyY3.mjs → dialect-helpers-Cue7QQOR.mjs} +21 -2
  338. package/dist/dialect-helpers-Cue7QQOR.mjs.map +1 -0
  339. package/dist/{email-console-CIf4Lfvv.mjs → email-console-CjkQc7oY.mjs} +1 -1
  340. package/dist/{email-console-CIf4Lfvv.mjs.map → email-console-CjkQc7oY.mjs.map} +1 -1
  341. package/dist/{enrich-BQ0mxJRs.mjs → enrich-CFJJgxs_.mjs} +2 -2
  342. package/dist/{enrich-BQ0mxJRs.mjs.map → enrich-CFJJgxs_.mjs.map} +1 -1
  343. package/dist/{error-CkAseSqZ.mjs → error-CfJMLSkd.mjs} +12 -3
  344. package/dist/error-CfJMLSkd.mjs.map +1 -0
  345. package/dist/{escape-DqD0aCKu.mjs → escape-r8_GbEU2.mjs} +1 -1
  346. package/dist/{escape-DqD0aCKu.mjs.map → escape-r8_GbEU2.mjs.map} +1 -1
  347. package/dist/{field-defs-cache-WUI88LX5.mjs → field-defs-cache-BVrNCgSj.mjs} +5 -5
  348. package/dist/{field-defs-cache-WUI88LX5.mjs.map → field-defs-cache-BVrNCgSj.mjs.map} +1 -1
  349. package/dist/{fts-manager-BbFaF_V8.mjs → fts-manager-BD9s77qh.mjs} +27 -3
  350. package/dist/{fts-manager-BbFaF_V8.mjs.map → fts-manager-BD9s77qh.mjs.map} +1 -1
  351. package/dist/{hash-DrvzIXcz.mjs → hash-DFFrkivP.mjs} +1 -1
  352. package/dist/{hash-DrvzIXcz.mjs.map → hash-DFFrkivP.mjs.map} +1 -1
  353. package/dist/hreflang-CgIxhH4k.d.mts +48 -0
  354. package/dist/hreflang-CgIxhH4k.d.mts.map +1 -0
  355. package/dist/hreflang-pAWUiPd_.mjs +102 -0
  356. package/dist/hreflang-pAWUiPd_.mjs.map +1 -0
  357. package/dist/{import-fYLf14Co.mjs → import-2jHtLzsO.mjs} +6 -6
  358. package/dist/{import-fYLf14Co.mjs.map → import-2jHtLzsO.mjs.map} +1 -1
  359. package/dist/{index-B9sIxwe4.d.mts → index-BXOCyLj0.d.mts} +115 -21
  360. package/dist/index-BXOCyLj0.d.mts.map +1 -0
  361. package/dist/{index-Bm-Gy8Mh.d.mts → index-DxjWLKGT.d.mts} +3 -3
  362. package/dist/{index-Bm-Gy8Mh.d.mts.map → index-DxjWLKGT.d.mts.map} +1 -1
  363. package/dist/index.d.mts +20 -19
  364. package/dist/index.mjs +68 -64
  365. package/dist/{init-lock-6b309ZrF.mjs → init-lock-DJkX6Hto.mjs} +1 -1
  366. package/dist/{init-lock-6b309ZrF.mjs.map → init-lock-DJkX6Hto.mjs.map} +1 -1
  367. package/dist/{load-Djn3HWQS.mjs → load-DgoWMdRX.mjs} +2 -2
  368. package/dist/{load-Djn3HWQS.mjs.map → load-DgoWMdRX.mjs.map} +1 -1
  369. package/dist/{loader-CB6YHeul.mjs → loader-Rb8QlGZp.mjs} +5 -20
  370. package/dist/loader-Rb8QlGZp.mjs.map +1 -0
  371. package/dist/{manifest-schema-BHlgZM1W.mjs → manifest-schema-DeGNma2O.mjs} +3 -1
  372. package/dist/manifest-schema-DeGNma2O.mjs.map +1 -0
  373. package/dist/media/image-endpoint.mjs +1 -1
  374. package/dist/media/index.d.mts +1 -1
  375. package/dist/media/index.mjs +2 -2
  376. package/dist/media/local-runtime.d.mts +13 -12
  377. package/dist/media/local-runtime.d.mts.map +1 -1
  378. package/dist/media/local-runtime.mjs +8 -8
  379. package/dist/{media-BrXIaMFN.mjs → media-D2ZFowpn.mjs} +2 -2
  380. package/dist/media-D2ZFowpn.mjs.map +1 -0
  381. package/dist/{media-allowlist-CRHEeJ_i.mjs → media-allowlist-Dc05vKtt.mjs} +2 -2
  382. package/dist/{media-allowlist-CRHEeJ_i.mjs.map → media-allowlist-Dc05vKtt.mjs.map} +1 -1
  383. package/dist/{media-url-CKi5XSw4.mjs → media-url-DgIEYZr4.mjs} +1 -1
  384. package/dist/{media-url-CKi5XSw4.mjs.map → media-url-DgIEYZr4.mjs.map} +1 -1
  385. package/dist/media-usage-B__v9NMm.mjs +421 -0
  386. package/dist/media-usage-B__v9NMm.mjs.map +1 -0
  387. package/dist/{menus-Cfivc2Ad.mjs → menus-BpfevlJD.mjs} +3 -3
  388. package/dist/{menus-Cfivc2Ad.mjs.map → menus-BpfevlJD.mjs.map} +1 -1
  389. package/dist/{menus-b2T4clbO.mjs → menus-BtvH7Z7t.mjs} +31 -12
  390. package/dist/menus-BtvH7Z7t.mjs.map +1 -0
  391. package/dist/{mime-CCEzze7W.mjs → mime-DfIVjNkr.mjs} +1 -1
  392. package/dist/{mime-CCEzze7W.mjs.map → mime-DfIVjNkr.mjs.map} +1 -1
  393. package/dist/{mode-BM9QgEmt.mjs → mode-BVnhGZ2o.mjs} +1 -1
  394. package/dist/{mode-BM9QgEmt.mjs.map → mode-BVnhGZ2o.mjs.map} +1 -1
  395. package/dist/{normalize-CeglrBT9.mjs → normalize-C-SHXmra.mjs} +1 -1
  396. package/dist/{normalize-CeglrBT9.mjs.map → normalize-C-SHXmra.mjs.map} +1 -1
  397. package/dist/{oauth-authorization-CXWkIGT4.mjs → oauth-authorization-BWqWTHBK.mjs} +5 -5
  398. package/dist/{oauth-authorization-CXWkIGT4.mjs.map → oauth-authorization-BWqWTHBK.mjs.map} +1 -1
  399. package/dist/{oauth-clients-BmTI5rsl.mjs → oauth-clients-43_XADoa.mjs} +1 -1
  400. package/dist/{oauth-clients-BmTI5rsl.mjs.map → oauth-clients-43_XADoa.mjs.map} +1 -1
  401. package/dist/{oauth-state-store-YLFLcH5F.mjs → oauth-state-store-BRSUEkmx.mjs} +1 -1
  402. package/dist/{oauth-state-store-YLFLcH5F.mjs.map → oauth-state-store-BRSUEkmx.mjs.map} +1 -1
  403. package/dist/{oauth-user-lookup-Bsc_GsX-.mjs → oauth-user-lookup-Ctgemb0k.mjs} +1 -1
  404. package/dist/{oauth-user-lookup-Bsc_GsX-.mjs.map → oauth-user-lookup-Ctgemb0k.mjs.map} +1 -1
  405. package/dist/object-cache/memory.d.mts +1 -1
  406. package/dist/{options-DTTML-Tx.mjs → options-BlmBHTvX.mjs} +1 -1
  407. package/dist/{options-DTTML-Tx.mjs.map → options-BlmBHTvX.mjs.map} +1 -1
  408. package/dist/{options-iMpRRxIe.d.mts → options-COXGTtqd.d.mts} +3 -3
  409. package/dist/{options-iMpRRxIe.d.mts.map → options-COXGTtqd.d.mts.map} +1 -1
  410. package/dist/page/index.d.mts +2 -2
  411. package/dist/{parse-GmjecGct.mjs → parse-GLksnn-B.mjs} +2 -2
  412. package/dist/{parse-GmjecGct.mjs.map → parse-GLksnn-B.mjs.map} +1 -1
  413. package/dist/{passkey-config-De0ahnRg.mjs → passkey-config-CjA1UbDj.mjs} +1 -1
  414. package/dist/{passkey-config-De0ahnRg.mjs.map → passkey-config-CjA1UbDj.mjs.map} +1 -1
  415. package/dist/{patterns-BKmjvM7K.mjs → patterns-CiyXeDgr.mjs} +1 -1
  416. package/dist/{patterns-BKmjvM7K.mjs.map → patterns-CiyXeDgr.mjs.map} +1 -1
  417. package/dist/{placeholder-BSKwzRe4.d.mts → placeholder-DR-eeZr-.d.mts} +1 -1
  418. package/dist/{placeholder-BSKwzRe4.d.mts.map → placeholder-DR-eeZr-.d.mts.map} +1 -1
  419. package/dist/plugin-types.d.mts +1 -1
  420. package/dist/plugin-utils.d.mts +11 -10
  421. package/dist/plugin-utils.d.mts.map +1 -1
  422. package/dist/plugins/adapt-sandbox-entry.d.mts +11 -10
  423. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  424. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  425. package/dist/{portable-text-cCa0cGV6.mjs → portable-text-BAkgEKcv.mjs} +1 -1
  426. package/dist/{portable-text-cCa0cGV6.mjs.map → portable-text-BAkgEKcv.mjs.map} +1 -1
  427. package/dist/{preview-SSXNVPAC.mjs → preview-Rloa09Wp.mjs} +2 -2
  428. package/dist/{preview-SSXNVPAC.mjs.map → preview-Rloa09Wp.mjs.map} +1 -1
  429. package/dist/{public-url-Danv47cS.mjs → public-url-C0cEvrRu.mjs} +1 -1
  430. package/dist/{public-url-Danv47cS.mjs.map → public-url-C0cEvrRu.mjs.map} +1 -1
  431. package/dist/{schema-CX9cfNkZ.mjs → query-BxT95opY.mjs} +5 -10
  432. package/dist/query-BxT95opY.mjs.map +1 -0
  433. package/dist/{query-1Cp6U9oo.mjs → query-ZzvXyxWq.mjs} +15 -15
  434. package/dist/query-ZzvXyxWq.mjs.map +1 -0
  435. package/dist/{rate-limit-C_Lf63Dd.mjs → rate-limit-Cg4Rvc_-.mjs} +2 -2
  436. package/dist/{rate-limit-C_Lf63Dd.mjs.map → rate-limit-Cg4Rvc_-.mjs.map} +1 -1
  437. package/dist/{redirect-CoW3G-0U.mjs → redirect-Ce_6Yb6z.mjs} +1 -1
  438. package/dist/{redirect-CoW3G-0U.mjs.map → redirect-Ce_6Yb6z.mjs.map} +1 -1
  439. package/dist/{redirect-IyfNirgS.mjs → redirect-Ch3E98qZ.mjs} +4 -4
  440. package/dist/{redirect-IyfNirgS.mjs.map → redirect-Ch3E98qZ.mjs.map} +1 -1
  441. package/dist/{redirects-Bs1g42Ri.mjs → redirects-CXmVGRNZ.mjs} +6 -6
  442. package/dist/{redirects-Bs1g42Ri.mjs.map → redirects-CXmVGRNZ.mjs.map} +1 -1
  443. package/dist/{redirects-DXEXBAII.mjs → redirects-CeD7K7EX.mjs} +43 -5
  444. package/dist/redirects-CeD7K7EX.mjs.map +1 -0
  445. package/dist/{registry-B57vp_X9.mjs → registry-CyIXPi4E.mjs} +8 -8
  446. package/dist/{registry-B57vp_X9.mjs.map → registry-CyIXPi4E.mjs.map} +1 -1
  447. package/dist/relation-Ba68Tm4Z.mjs +334 -0
  448. package/dist/relation-Ba68Tm4Z.mjs.map +1 -0
  449. package/dist/{byline-fields-2Lp5Hbgf.mjs → relations-BmBmX4Eb.mjs} +60 -4
  450. package/dist/relations-BmBmX4Eb.mjs.map +1 -0
  451. package/dist/relations-C8hVyEy0.mjs +397 -0
  452. package/dist/relations-C8hVyEy0.mjs.map +1 -0
  453. package/dist/{byline-fields-CHE8UStY.d.mts → relations-CbBpoOys.d.mts} +182 -29
  454. package/dist/relations-CbBpoOys.d.mts.map +1 -0
  455. package/dist/{request-cache-KCNHp_RJ.mjs → request-cache-BSUptuJR.mjs} +1 -1
  456. package/dist/{request-cache-KCNHp_RJ.mjs.map → request-cache-BSUptuJR.mjs.map} +1 -1
  457. package/dist/{request-meta-DbvXaAvU.mjs → request-meta-DCwktC5P.mjs} +2 -2
  458. package/dist/{request-meta-DbvXaAvU.mjs.map → request-meta-DCwktC5P.mjs.map} +1 -1
  459. package/dist/{resolve-DR3wqMTy.mjs → resolve-Cd9dzclN.mjs} +1 -1
  460. package/dist/{resolve-DR3wqMTy.mjs.map → resolve-Cd9dzclN.mjs.map} +1 -1
  461. package/dist/{runner-DvFWoz8-.d.mts → runner-CiNdIk3y.d.mts} +8 -2
  462. package/dist/runner-CiNdIk3y.d.mts.map +1 -0
  463. package/dist/{runner-K8XK101B.mjs → runner-jt65bF7F.mjs} +24 -7
  464. package/dist/{runner-K8XK101B.mjs.map → runner-jt65bF7F.mjs.map} +1 -1
  465. package/dist/runtime.d.mts +12 -11
  466. package/dist/runtime.d.mts.map +1 -1
  467. package/dist/runtime.mjs +4 -4
  468. package/dist/{scheduled-publish-Drffrf2a.d.mts → scheduled-publish-DlJHGcuL.d.mts} +1 -1
  469. package/dist/scheduled-publish-DlJHGcuL.d.mts.map +1 -0
  470. package/dist/schema-DwjufZ1S.mjs +9 -0
  471. package/dist/schema-DwjufZ1S.mjs.map +1 -0
  472. package/dist/{search-DZN2HZ-V.mjs → search-Cl875CVE.mjs} +62 -12
  473. package/dist/search-Cl875CVE.mjs.map +1 -0
  474. package/dist/{secrets-kp760LlD.mjs → secrets-CO_bEmII.mjs} +4 -4
  475. package/dist/{secrets-kp760LlD.mjs.map → secrets-CO_bEmII.mjs.map} +1 -1
  476. package/dist/{sections-CpF5SaGr.mjs → sections-DTKzgoiS.mjs} +3 -3
  477. package/dist/{sections-CpF5SaGr.mjs.map → sections-DTKzgoiS.mjs.map} +1 -1
  478. package/dist/seed/index.d.mts +2 -2
  479. package/dist/seed/index.mjs +23 -23
  480. package/dist/seo/index.d.mts +4 -2
  481. package/dist/seo/index.d.mts.map +1 -1
  482. package/dist/seo/index.mjs +19 -2
  483. package/dist/seo/index.mjs.map +1 -1
  484. package/dist/{seo-DXu2UQnC.mjs → seo-BLm6SXzS.mjs} +2 -2
  485. package/dist/{seo-DXu2UQnC.mjs.map → seo-BLm6SXzS.mjs.map} +1 -1
  486. package/dist/{seo-89-53DDs.mjs → seo-BjrbXAd_.mjs} +1 -1
  487. package/dist/{seo-89-53DDs.mjs.map → seo-BjrbXAd_.mjs.map} +1 -1
  488. package/dist/{service-DQmiBMzt.mjs → service-D6zDmKXy.mjs} +3 -3
  489. package/dist/{service-DQmiBMzt.mjs.map → service-D6zDmKXy.mjs.map} +1 -1
  490. package/dist/{session-user-Cascbax7.mjs → session-user-DoSs6YM8.mjs} +1 -1
  491. package/dist/{session-user-Cascbax7.mjs.map → session-user-DoSs6YM8.mjs.map} +1 -1
  492. package/dist/{settings-zhmdKyJQ.mjs → settings-BuQYvLfy.mjs} +6 -6
  493. package/dist/{settings-zhmdKyJQ.mjs.map → settings-BuQYvLfy.mjs.map} +1 -1
  494. package/dist/{settings-rZyHEcNT.mjs → settings-CmS3TwXf.mjs} +3 -3
  495. package/dist/{settings-rZyHEcNT.mjs.map → settings-CmS3TwXf.mjs.map} +1 -1
  496. package/dist/{setup-complete-Cb3LRyzy.mjs → setup-complete-BPNgLHlU.mjs} +2 -2
  497. package/dist/{setup-complete-Cb3LRyzy.mjs.map → setup-complete-BPNgLHlU.mjs.map} +1 -1
  498. package/dist/{setup-nonce-Z8x0tHf8.mjs → setup-nonce-DEPZX96T.mjs} +1 -1
  499. package/dist/{setup-nonce-Z8x0tHf8.mjs.map → setup-nonce-DEPZX96T.mjs.map} +1 -1
  500. package/dist/{single-flight-cache-BG3B_GhN.mjs → single-flight-cache-DEtW-OEG.mjs} +2 -2
  501. package/dist/{single-flight-cache-BG3B_GhN.mjs.map → single-flight-cache-DEtW-OEG.mjs.map} +1 -1
  502. package/dist/{site-url-BBfDTgZB.mjs → site-url-CcLy5D5s.mjs} +2 -2
  503. package/dist/{site-url-BBfDTgZB.mjs.map → site-url-CcLy5D5s.mjs.map} +1 -1
  504. package/dist/{slugify-Cce3dTdg.mjs → slugify-C_tqlU4G.mjs} +1 -1
  505. package/dist/{slugify-Cce3dTdg.mjs.map → slugify-C_tqlU4G.mjs.map} +1 -1
  506. package/dist/{ssrf-1g7usGBg.mjs → ssrf-TrKYJAy0.mjs} +1 -1
  507. package/dist/{ssrf-1g7usGBg.mjs.map → ssrf-TrKYJAy0.mjs.map} +1 -1
  508. package/dist/{status-CqJQX1QN.mjs → status-D3Gy1Zc6.mjs} +1 -1
  509. package/dist/{status-CqJQX1QN.mjs.map → status-D3Gy1Zc6.mjs.map} +1 -1
  510. package/dist/storage/local.d.mts +1 -1
  511. package/dist/storage/local.mjs +1 -1
  512. package/dist/storage/s3.d.mts +1 -1
  513. package/dist/storage/s3.mjs +1 -1
  514. package/dist/{taxonomies-Bs13SN-F.mjs → taxonomies-ClS4RKaZ.mjs} +13 -9
  515. package/dist/taxonomies-ClS4RKaZ.mjs.map +1 -0
  516. package/dist/{taxonomies-kH9ABDUF.mjs → taxonomies-pUkuD6nj.mjs} +115 -36
  517. package/dist/taxonomies-pUkuD6nj.mjs.map +1 -0
  518. package/dist/{taxonomy-BxGxZZdu.mjs → taxonomy-BkxbUMSw.mjs} +12 -3
  519. package/dist/{taxonomy-BxGxZZdu.mjs.map → taxonomy-BkxbUMSw.mjs.map} +1 -1
  520. package/dist/{tokens-BGT0TUUo.mjs → tokens-BV6fgl5A.mjs} +2 -2
  521. package/dist/{tokens-BGT0TUUo.mjs.map → tokens-BV6fgl5A.mjs.map} +1 -1
  522. package/dist/{transaction-qfqpPVpu.mjs → transaction-D0FOsb3X.mjs} +1 -1
  523. package/dist/{transaction-qfqpPVpu.mjs.map → transaction-D0FOsb3X.mjs.map} +1 -1
  524. package/dist/{transport-DPefPMcz.mjs → transport-DTFhhPOg.mjs} +1 -1
  525. package/dist/{transport-DPefPMcz.mjs.map → transport-DTFhhPOg.mjs.map} +1 -1
  526. package/dist/{transport-BMDWHSgZ.d.mts → transport-DmlmzD2U.d.mts} +1 -1
  527. package/dist/{transport-BMDWHSgZ.d.mts.map → transport-DmlmzD2U.d.mts.map} +1 -1
  528. package/dist/{trusted-proxy-VTPRiX2s.mjs → trusted-proxy-CSgZVESw.mjs} +1 -1
  529. package/dist/{trusted-proxy-VTPRiX2s.mjs.map → trusted-proxy-CSgZVESw.mjs.map} +1 -1
  530. package/dist/{types-CgvhU-hF.d.mts → types-B6-N9cBB.d.mts} +1 -1
  531. package/dist/{types-CgvhU-hF.d.mts.map → types-B6-N9cBB.d.mts.map} +1 -1
  532. package/dist/{types-g_aFIdW0.d.mts → types-BCVuUPrL.d.mts} +1 -1
  533. package/dist/{types-g_aFIdW0.d.mts.map → types-BCVuUPrL.d.mts.map} +1 -1
  534. package/dist/{types-Jo3UYd6b.mjs → types-BNZsPL9z.mjs} +1 -1
  535. package/dist/{types-Jo3UYd6b.mjs.map → types-BNZsPL9z.mjs.map} +1 -1
  536. package/dist/{types-vgEWF18A.d.mts → types-B_pmP7Yq.d.mts} +1 -1
  537. package/dist/{types-vgEWF18A.d.mts.map → types-B_pmP7Yq.d.mts.map} +1 -1
  538. package/dist/{types-CuW0zWDA.mjs → types-Ch8pFMEm.mjs} +3 -3
  539. package/dist/{types-CuW0zWDA.mjs.map → types-Ch8pFMEm.mjs.map} +1 -1
  540. package/dist/{types-ea9sAPck.d.mts → types-Cm4Cz-8M.d.mts} +1 -1
  541. package/dist/{types-ea9sAPck.d.mts.map → types-Cm4Cz-8M.d.mts.map} +1 -1
  542. package/dist/{types-CGbYowGQ.d.mts → types-CtDVagM4.d.mts} +55 -3
  543. package/dist/types-CtDVagM4.d.mts.map +1 -0
  544. package/dist/{types-BcLUUbbR.d.mts → types-DLIrNtv5.d.mts} +11 -3
  545. package/dist/{types-BcLUUbbR.d.mts.map → types-DLIrNtv5.d.mts.map} +1 -1
  546. package/dist/{types-DmHk7vAt.d.mts → types-QnlQIyPE.d.mts} +1 -1
  547. package/dist/{types-DmHk7vAt.d.mts.map → types-QnlQIyPE.d.mts.map} +1 -1
  548. package/dist/{types-_HFLRzTn.d.mts → types-momGysZk.d.mts} +1 -1
  549. package/dist/{types-_HFLRzTn.d.mts.map → types-momGysZk.d.mts.map} +1 -1
  550. package/dist/{types-tM44hEcf.mjs → types-o7xo7VgH.mjs} +1 -1
  551. package/dist/{types-tM44hEcf.mjs.map → types-o7xo7VgH.mjs.map} +1 -1
  552. package/dist/{types-ClUkdbWb.d.mts → types-qljd1hsL.d.mts} +1 -1
  553. package/dist/{types-ClUkdbWb.d.mts.map → types-qljd1hsL.d.mts.map} +1 -1
  554. package/dist/{user-BjAOvU3Z.mjs → user-DTxX_Fha.mjs} +3 -3
  555. package/dist/{user-BjAOvU3Z.mjs.map → user-DTxX_Fha.mjs.map} +1 -1
  556. package/dist/{utils-BJXElxdw.mjs → utils-Cz8PVrl4.mjs} +3 -3
  557. package/dist/{utils-BJXElxdw.mjs.map → utils-Cz8PVrl4.mjs.map} +1 -1
  558. package/dist/{validate-Z1b86_3B.d.mts → validate-9jLuE301.d.mts} +5 -5
  559. package/dist/{validate-Z1b86_3B.d.mts.map → validate-9jLuE301.d.mts.map} +1 -1
  560. package/dist/{validate-CWSnKVIJ.mjs → validate-C3CpWlNt.mjs} +3 -3
  561. package/dist/{validate-CWSnKVIJ.mjs.map → validate-C3CpWlNt.mjs.map} +1 -1
  562. package/dist/{validation-0l8-RYj4.mjs → validation-DM2K_NJC.mjs} +10 -10
  563. package/dist/{validation-0l8-RYj4.mjs.map → validation-DM2K_NJC.mjs.map} +1 -1
  564. package/dist/version-BlIJvNAZ.mjs +7 -0
  565. package/dist/{version-qmeO1XZY.mjs.map → version-BlIJvNAZ.mjs.map} +1 -1
  566. package/dist/{widgets-hWcIicX8.mjs → widgets-B_X6_UXJ.mjs} +4 -4
  567. package/dist/{widgets-hWcIicX8.mjs.map → widgets-B_X6_UXJ.mjs.map} +1 -1
  568. package/dist/{wxr-taxonomies-CNFcsNCr.mjs → wxr-taxonomies-oNJB2S81.mjs} +4 -4
  569. package/dist/{wxr-taxonomies-CNFcsNCr.mjs.map → wxr-taxonomies-oNJB2S81.mjs.map} +1 -1
  570. package/dist/{wxr-taxonomies-0_uxSrmf.d.mts → wxr-taxonomies-tADW4KTa.d.mts} +1 -1
  571. package/dist/{wxr-taxonomies-0_uxSrmf.d.mts.map → wxr-taxonomies-tADW4KTa.d.mts.map} +1 -1
  572. package/dist/{zod-generator-CacrR-i8.mjs → zod-generator-CrfV79De.mjs} +3 -3
  573. package/dist/{zod-generator-CacrR-i8.mjs.map → zod-generator-CrfV79De.mjs.map} +1 -1
  574. package/package.json +11 -7
  575. package/src/api/errors.ts +11 -0
  576. package/src/api/handlers/content.ts +111 -1
  577. package/src/api/handlers/dashboard.ts +2 -0
  578. package/src/api/handlers/index.ts +6 -0
  579. package/src/api/handlers/media-usage.ts +80 -0
  580. package/src/api/handlers/relations.ts +474 -0
  581. package/src/api/handlers/taxonomies.ts +26 -4
  582. package/src/api/handlers/validate-media-fields.ts +1 -1
  583. package/src/api/openapi/document.ts +25 -0
  584. package/src/api/parse.ts +1 -1
  585. package/src/api/schemas/comments.ts +6 -0
  586. package/src/api/schemas/content.ts +8 -0
  587. package/src/api/schemas/index.ts +2 -0
  588. package/src/api/schemas/media-usage.ts +48 -0
  589. package/src/api/schemas/relations.ts +109 -0
  590. package/src/api/schemas/search.ts +1 -0
  591. package/src/astro/integration/index.ts +1 -0
  592. package/src/astro/integration/routes.ts +5 -0
  593. package/src/astro/integration/runtime.ts +24 -0
  594. package/src/astro/middleware/request-context.ts +162 -20
  595. package/src/astro/middleware.ts +22 -9
  596. package/src/astro/routes/api/admin/comments/[id]/status.ts +1 -2
  597. package/src/astro/routes/api/admin/comments/index.ts +1 -2
  598. package/src/astro/routes/api/admin/media-usage/repair.ts +24 -0
  599. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +1 -2
  600. package/src/astro/routes/api/auth/oauth/[provider].ts +1 -2
  601. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +13 -0
  602. package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/children.ts +97 -0
  603. package/src/astro/routes/api/content/[collection]/[id]/references/[relation]/parents.ts +48 -0
  604. package/src/astro/routes/api/relations/[id]/index.ts +84 -0
  605. package/src/astro/routes/api/relations/[id]/translations.ts +33 -0
  606. package/src/astro/routes/api/relations/index.ts +56 -0
  607. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +1 -7
  608. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +1 -2
  609. package/src/astro/routes/api/search/index.ts +2 -0
  610. package/src/astro/types.ts +2 -0
  611. package/src/cli/commands/export-seed.ts +1 -10
  612. package/src/client/index.ts +39 -0
  613. package/src/comments/service.ts +1 -1
  614. package/src/comments/turnstile.ts +64 -0
  615. package/src/components/BlockquoteGroup.astro +34 -0
  616. package/src/components/CommentForm.astro +12 -5
  617. package/src/components/EmDashHead.astro +27 -1
  618. package/src/components/InlinePortableTextEditor.tsx +34 -0
  619. package/src/components/PortableText.astro +7 -1
  620. package/src/components/index.ts +2 -0
  621. package/src/components/portable-text-blockquote-group.ts +68 -0
  622. package/src/content/converters/portable-text-to-prosemirror.ts +32 -1
  623. package/src/database/dialect-helpers.ts +34 -0
  624. package/src/database/migrations/039_fix_fts5_triggers.ts +1 -1
  625. package/src/database/migrations/runner.ts +46 -6
  626. package/src/database/pg-migration-lock.ts +72 -0
  627. package/src/database/repositories/comment.ts +1 -1
  628. package/src/database/repositories/content.ts +172 -21
  629. package/src/database/repositories/media.ts +1 -1
  630. package/src/database/repositories/relation.ts +130 -5
  631. package/src/database/repositories/taxonomy.ts +9 -0
  632. package/src/database/repositories/types.ts +8 -0
  633. package/src/db/postgres.ts +5 -2
  634. package/src/emdash-runtime.ts +72 -4
  635. package/src/index.ts +6 -2
  636. package/src/loader.ts +1 -31
  637. package/src/mcp/server.ts +29 -3
  638. package/src/plugins/context.ts +85 -0
  639. package/src/plugins/define-plugin.ts +1 -0
  640. package/src/plugins/index.ts +4 -0
  641. package/src/plugins/manifest-schema.ts +2 -0
  642. package/src/plugins/types.ts +59 -0
  643. package/src/schema/byline-registry.ts +1 -1
  644. package/src/search/fts-manager.ts +34 -0
  645. package/src/search/match.ts +50 -0
  646. package/src/search/query.ts +94 -9
  647. package/src/seo/hreflang.ts +192 -0
  648. package/src/seo/index.ts +3 -0
  649. package/src/taxonomies/index.ts +62 -45
  650. package/src/taxonomies/term-counts.ts +117 -0
  651. package/src/virtual-modules.d.ts +1 -0
  652. package/src/visual-editing/editable.ts +41 -35
  653. package/src/visual-editing/toolbar-bootstrap.ts +120 -0
  654. package/src/visual-editing/toolbar.ts +46 -0
  655. package/dist/api-DxFSorgk.mjs.map +0 -1
  656. package/dist/byline-fields-2Lp5Hbgf.mjs.map +0 -1
  657. package/dist/byline-fields-CHE8UStY.d.mts.map +0 -1
  658. package/dist/content-DhgEth4k.mjs.map +0 -1
  659. package/dist/context-BRIpHARC.mjs.map +0 -1
  660. package/dist/dashboard-EvEKF5en.mjs.map +0 -1
  661. package/dist/dialect-helpers-DRI5pyY3.mjs.map +0 -1
  662. package/dist/error-CkAseSqZ.mjs.map +0 -1
  663. package/dist/index-B9sIxwe4.d.mts.map +0 -1
  664. package/dist/loader-CB6YHeul.mjs.map +0 -1
  665. package/dist/manifest-schema-BHlgZM1W.mjs.map +0 -1
  666. package/dist/media-BrXIaMFN.mjs.map +0 -1
  667. package/dist/menus-b2T4clbO.mjs.map +0 -1
  668. package/dist/query-1Cp6U9oo.mjs.map +0 -1
  669. package/dist/redirects-DXEXBAII.mjs.map +0 -1
  670. package/dist/runner-DvFWoz8-.d.mts.map +0 -1
  671. package/dist/scheduled-publish-Drffrf2a.d.mts.map +0 -1
  672. package/dist/schema-CX9cfNkZ.mjs.map +0 -1
  673. package/dist/search-DZN2HZ-V.mjs.map +0 -1
  674. package/dist/taxonomies-Bs13SN-F.mjs.map +0 -1
  675. package/dist/taxonomies-kH9ABDUF.mjs.map +0 -1
  676. package/dist/types-CGbYowGQ.d.mts.map +0 -1
  677. package/dist/version-qmeO1XZY.mjs +0 -7
  678. /package/dist/{api-tokens-DsvvCtdh.mjs → api-tokens-Dk2GrJMY.mjs} +0 -0
  679. /package/dist/{ssrf-CmuGgCq3.mjs → ssrf-B5DOv5E5.mjs} +0 -0
  680. /package/dist/{types-DROWRxYV.mjs → types-DeCZBZxn.mjs} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"request-context.mjs","names":[],"sources":["../../../src/visual-editing/toolbar.ts","../../../src/astro/middleware/request-context.ts"],"sourcesContent":["/**\n * EmDash Visual Editing Toolbar\n *\n * A floating pill injected via middleware for authenticated editors.\n * Renders as a plain HTML string with inline styles and a <script> tag.\n * No dependencies — works on any page with a </body> tag.\n */\n\ninterface ToolbarConfig {\n\teditMode: boolean;\n\tisPreview: boolean;\n}\n\nexport function renderToolbar(config: ToolbarConfig): string {\n\tconst { editMode, isPreview } = config;\n\n\treturn `\n<!-- EmDash Visual Editing Toolbar -->\n<div id=\"emdash-toolbar\" data-edit-mode=\"${editMode}\" data-preview=\"${isPreview}\">\n <div class=\"emdash-tb-inner\">\n <span class=\"emdash-tb-logo\">EmDash</span>\n\n <div class=\"emdash-tb-divider\"></div>\n\n <label class=\"emdash-tb-toggle\" title=\"Toggle edit mode\">\n <input type=\"checkbox\" id=\"emdash-edit-toggle\" ${editMode ? \"checked\" : \"\"} />\n <span class=\"emdash-tb-toggle-track\">\n <span class=\"emdash-tb-toggle-thumb\"></span>\n </span>\n <span class=\"emdash-tb-toggle-label\">Edit</span>\n </label>\n\n <span class=\"emdash-tb-status\" id=\"emdash-tb-status\"></span>\n\n <span class=\"emdash-tb-save-status\" id=\"emdash-tb-save-status\"></span>\n\n <a class=\"emdash-tb-admin\" id=\"emdash-tb-admin\" href=\"#\" target=\"emdash-admin\" style=\"display:none\" title=\"Open in admin\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/><polyline points=\"15 3 21 3 21 9\"/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"/></svg>\n </a>\n\n <button class=\"emdash-tb-publish\" id=\"emdash-tb-publish\" style=\"display:none\">Publish</button>\n </div>\n</div>\n\n<style>\n #emdash-toolbar {\n position: fixed;\n bottom: 16px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n font-size: 13px;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n }\n\n .emdash-tb-inner {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 16px;\n background: #1a1a1a;\n color: #e0e0e0;\n border-radius: 999px;\n box-shadow: 0 4px 24px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.08);\n white-space: nowrap;\n user-select: none;\n }\n\n .emdash-tb-logo {\n font-weight: 600;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: #fff;\n opacity: 0.7;\n }\n\n .emdash-tb-divider {\n width: 1px;\n height: 16px;\n background: rgba(255,255,255,0.15);\n }\n\n /* Toggle switch */\n .emdash-tb-toggle {\n display: flex;\n align-items: center;\n gap: 6px;\n cursor: pointer;\n }\n\n .emdash-tb-toggle input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .emdash-tb-toggle-track {\n position: relative;\n width: 32px;\n height: 18px;\n background: #444;\n border-radius: 9px;\n transition: background 0.2s;\n }\n\n .emdash-tb-toggle input:checked + .emdash-tb-toggle-track {\n background: #3b82f6;\n }\n\n .emdash-tb-toggle-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 14px;\n height: 14px;\n background: #fff;\n border-radius: 50%;\n transition: transform 0.2s;\n }\n\n .emdash-tb-toggle input:checked + .emdash-tb-toggle-track .emdash-tb-toggle-thumb {\n transform: translateX(14px);\n }\n\n .emdash-tb-toggle-label {\n font-size: 12px;\n color: #aaa;\n }\n\n .emdash-tb-toggle input:checked ~ .emdash-tb-toggle-label {\n color: #fff;\n }\n\n /* Status area — flex for multiple badges */\n .emdash-tb-status {\n display: inline-flex;\n gap: 6px;\n align-items: center;\n }\n\n /* Badges */\n .emdash-tb-badge {\n display: inline-flex;\n align-items: center;\n padding: 3px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n }\n\n .emdash-tb-badge--preview {\n background: rgba(139,92,246,0.2);\n color: #a78bfa;\n }\n\n .emdash-tb-badge--draft {\n background: rgba(245,158,11,0.2);\n color: #fbbf24;\n }\n\n .emdash-tb-badge--published {\n background: rgba(34,197,94,0.2);\n color: #4ade80;\n }\n\n .emdash-tb-badge--pending {\n background: rgba(59,130,246,0.2);\n color: #60a5fa;\n }\n\n .emdash-tb-badge--unsaved {\n background: rgba(245,158,11,0.2);\n color: #fbbf24;\n }\n\n .emdash-tb-badge--saving {\n background: rgba(148,163,184,0.2);\n color: #94a3b8;\n }\n\n .emdash-tb-badge--saved {\n background: rgba(34,197,94,0.2);\n color: #4ade80;\n transition: opacity 0.3s;\n }\n\n .emdash-tb-badge--error {\n background: rgba(239,68,68,0.2);\n color: #f87171;\n }\n\n /* Admin link */\n .emdash-tb-admin {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: #888;\n text-decoration: none;\n padding: 2px;\n border-radius: 4px;\n transition: color 0.15s;\n }\n\n .emdash-tb-admin:hover {\n color: #fff;\n }\n\n /* Publish button */\n .emdash-tb-publish {\n padding: 4px 12px;\n background: #3b82f6;\n color: #fff;\n border: none;\n border-radius: 999px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background 0.15s;\n font-family: inherit;\n }\n\n .emdash-tb-publish:hover {\n background: #2563eb;\n }\n\n .emdash-tb-publish:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n /* Edit mode: editable hover styles — uses :has() to check toolbar state */\n body:has(#emdash-toolbar[data-edit-mode=\"true\"]) [data-emdash-ref] {\n transition: box-shadow 0.15s, background-color 0.15s;\n }\n\n body:has(#emdash-toolbar[data-edit-mode=\"true\"]) [data-emdash-ref]:hover {\n box-shadow: 0 0 0 2px rgba(59,130,246,0.5);\n border-radius: 4px;\n background-color: rgba(59,130,246,0.04);\n cursor: text;\n }\n\n /* Active editing state — override hover pencil cursor */\n [data-emdash-editing] {\n box-shadow: 0 0 0 2px #3b82f6 !important;\n border-radius: 4px !important;\n background-color: rgba(59,130,246,0.04) !important;\n cursor: text !important;\n }\n\n /* Suppress browser focus ring on contenteditable and tiptap editor */\n [data-emdash-editing]:focus,\n [data-emdash-ref] .tiptap:focus,\n [data-emdash-ref] .ProseMirror:focus {\n outline: none !important;\n }\n\n /* Image editor popover */\n .emdash-img-popover {\n position: fixed;\n z-index: 1000000;\n background: #1a1a1a;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08);\n color: #e0e0e0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n font-size: 13px;\n width: 320px;\n overflow: hidden;\n animation: emdash-img-fadein 0.15s ease-out;\n }\n\n @keyframes emdash-img-fadein {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n .emdash-img-popover-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n border-bottom: 1px solid rgba(255,255,255,0.08);\n }\n\n .emdash-img-popover-title {\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: #999;\n }\n\n .emdash-img-popover-close {\n background: none;\n border: none;\n color: #666;\n cursor: pointer;\n padding: 2px;\n line-height: 1;\n font-size: 16px;\n border-radius: 4px;\n transition: color 0.15s;\n }\n\n .emdash-img-popover-close:hover {\n color: #fff;\n }\n\n .emdash-img-popover-body {\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .emdash-img-preview {\n width: 100%;\n max-height: 160px;\n object-fit: contain;\n border-radius: 6px;\n background: #111;\n }\n\n .emdash-img-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 80px;\n border: 2px dashed rgba(255,255,255,0.15);\n border-radius: 6px;\n color: #666;\n font-size: 12px;\n }\n\n .emdash-img-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .emdash-img-field label {\n font-size: 11px;\n font-weight: 600;\n color: #888;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n }\n\n .emdash-img-field input[type=\"text\"] {\n background: #111;\n border: 1px solid rgba(255,255,255,0.12);\n border-radius: 6px;\n color: #e0e0e0;\n padding: 6px 8px;\n font-size: 13px;\n font-family: inherit;\n outline: none;\n transition: border-color 0.15s;\n }\n\n .emdash-img-field input[type=\"text\"]:focus {\n border-color: #3b82f6;\n }\n\n .emdash-img-actions {\n display: flex;\n gap: 6px;\n }\n\n .emdash-img-btn {\n flex: 1;\n padding: 6px 10px;\n border: 1px solid rgba(255,255,255,0.12);\n border-radius: 6px;\n background: #222;\n color: #e0e0e0;\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n text-align: center;\n white-space: nowrap;\n }\n\n .emdash-img-btn:hover {\n background: #333;\n border-color: rgba(255,255,255,0.2);\n }\n\n .emdash-img-btn--primary {\n background: #3b82f6;\n border-color: #3b82f6;\n color: #fff;\n }\n\n .emdash-img-btn--primary:hover {\n background: #2563eb;\n border-color: #2563eb;\n }\n\n .emdash-img-btn--danger {\n color: #f87171;\n border-color: rgba(248,113,113,0.3);\n }\n\n .emdash-img-btn--danger:hover {\n background: rgba(248,113,113,0.1);\n border-color: rgba(248,113,113,0.5);\n }\n\n /* Media browser within the popover */\n .emdash-img-browser {\n border-top: 1px solid rgba(255,255,255,0.08);\n padding: 12px;\n }\n\n .emdash-img-browser-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 8px;\n }\n\n .emdash-img-browser-title {\n font-size: 12px;\n font-weight: 600;\n color: #999;\n }\n\n .emdash-img-browser-back {\n background: none;\n border: none;\n color: #3b82f6;\n cursor: pointer;\n font-size: 12px;\n font-family: inherit;\n padding: 2px 4px;\n }\n\n .emdash-img-browser-back:hover {\n text-decoration: underline;\n }\n\n .emdash-img-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 6px;\n max-height: 240px;\n overflow-y: auto;\n }\n\n .emdash-img-grid-item {\n aspect-ratio: 1;\n border-radius: 4px;\n overflow: hidden;\n cursor: pointer;\n border: 2px solid transparent;\n transition: border-color 0.15s;\n background: #111;\n }\n\n .emdash-img-grid-item:hover {\n border-color: rgba(59,130,246,0.5);\n }\n\n .emdash-img-grid-item--selected {\n border-color: #3b82f6;\n }\n\n .emdash-img-grid-item img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n .emdash-img-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 80px;\n color: #666;\n font-size: 12px;\n }\n\n .emdash-img-drop {\n border: 2px dashed #3b82f6;\n background: rgba(59,130,246,0.05);\n }\n\n .emdash-img-uploading {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 0;\n color: #999;\n font-size: 12px;\n }\n\n .emdash-img-popover-backdrop {\n position: fixed;\n inset: 0;\n z-index: 999999;\n }\n</style>\n\n<script>\n(function() {\n var toolbar = document.getElementById(\"emdash-toolbar\");\n var toggle = document.getElementById(\"emdash-edit-toggle\");\n var statusEl = document.getElementById(\"emdash-tb-status\");\n var saveStatusEl = document.getElementById(\"emdash-tb-save-status\");\n var publishBtn = document.getElementById(\"emdash-tb-publish\");\n if (!toolbar || !toggle || !statusEl || !publishBtn || !saveStatusEl) return;\n\n var isEditMode = toolbar.getAttribute(\"data-edit-mode\") === \"true\";\n\n // CSRF-protected fetch — adds X-EmDash-Request header to all API calls\n function ecFetch(url, init) {\n init = init || {};\n init.headers = Object.assign({ \"X-EmDash-Request\": \"1\" }, init.headers || {});\n return fetch(url, init);\n }\n\n // --- Save status tracking ---\n var saveState = \"idle\"; // idle | unsaved | saving | saved | error\n var saveHideTimer = null;\n var pendingSavePromise = null;\n\n function setSaveState(state) {\n saveState = state;\n clearTimeout(saveHideTimer);\n\n switch (state) {\n case \"unsaved\":\n saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--unsaved\">Unsaved</span>';\n break;\n case \"saving\":\n saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--saving\">Saving\\u2026</span>';\n break;\n case \"saved\":\n saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--saved\">Saved</span>';\n saveHideTimer = setTimeout(function() {\n saveStatusEl.innerHTML = \"\";\n saveState = \"idle\";\n }, 2000);\n break;\n case \"error\":\n saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--error\">Save failed</span>';\n saveHideTimer = setTimeout(function() {\n saveStatusEl.innerHTML = \"\";\n saveState = \"idle\";\n }, 3000);\n break;\n default:\n saveStatusEl.innerHTML = \"\";\n }\n }\n\n // Listen for save events from inline editors (e.g. PT editor)\n document.addEventListener(\"emdash:save\", function(e) {\n var detail = e.detail || {};\n if (detail.state) {\n setSaveState(detail.state);\n }\n });\n\n document.addEventListener(\"emdash:content-changed\", function(e) {\n var detail = e.detail || {};\n if (detail.collection && detail.id) {\n showUnpublishedChanges(detail.collection, detail.id);\n }\n });\n\n // --- Entry status ---\n var entryRef = null;\n\n function updateStatus() {\n if (!isEditMode) {\n statusEl.innerHTML = \"\";\n publishBtn.style.display = \"none\";\n return;\n }\n\n var first = document.querySelector(\"[data-emdash-ref]\");\n if (!first) {\n statusEl.innerHTML = \"\";\n publishBtn.style.display = \"none\";\n return;\n }\n\n try {\n var ref = JSON.parse(first.getAttribute(\"data-emdash-ref\"));\n entryRef = ref;\n if (!ref.status) return;\n\n // Show admin link\n var adminLink = document.getElementById(\"emdash-tb-admin\");\n if (adminLink) {\n adminLink.href = \"/_emdash/admin/content/\" + encodeURIComponent(ref.collection) + \"/\" + encodeURIComponent(ref.id);\n adminLink.style.display = \"\";\n }\n\n if (ref.status === \"draft\") {\n statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--draft\">Draft</span>';\n publishBtn.style.display = \"\";\n publishBtn.onclick = function() { publish(ref.collection, ref.id); };\n } else if (ref.status === \"published\" && ref.hasDraft) {\n statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--pending\">Unpublished changes</span>';\n publishBtn.style.display = \"\";\n publishBtn.onclick = function() { publish(ref.collection, ref.id); };\n } else if (ref.status === \"published\") {\n statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--published\">Published</span>';\n publishBtn.style.display = \"none\";\n }\n } catch (e) {\n // ignore parse errors\n }\n }\n\n // Publish action\n function publish(collection, id) {\n if (pendingSavePromise) {\n pendingSavePromise.then(function() { publish(collection, id); });\n return;\n }\n\n publishBtn.disabled = true;\n publishBtn.textContent = \"Publishing\\u2026\";\n\n ecFetch(\"/_emdash/api/content/\" + encodeURIComponent(collection) + \"/\" + encodeURIComponent(id) + \"/publish\", {\n method: \"POST\",\n credentials: \"same-origin\",\n })\n .then(function(res) {\n if (res.ok) {\n if (document.startViewTransition) {\n document.startViewTransition(function() { location.reload(); });\n } else {\n location.reload();\n }\n } else {\n publishBtn.disabled = false;\n publishBtn.textContent = \"Publish\";\n console.error(\"Publish failed:\", res.status);\n }\n })\n .catch(function(err) {\n publishBtn.disabled = false;\n publishBtn.textContent = \"Publish\";\n console.error(\"Publish failed:\", err);\n });\n }\n\n // Edit mode toggle\n toggle.addEventListener(\"change\", function() {\n if (toggle.checked) {\n document.cookie = \"emdash-edit-mode=true;path=/;samesite=lax\";\n } else {\n document.cookie = \"emdash-edit-mode=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT\";\n }\n\n if (document.startViewTransition) {\n document.startViewTransition(function() { location.replace(location.href); });\n } else {\n location.replace(location.href);\n }\n });\n\n // --- Inline editing ---\n\n // Cached manifest (fetched once on first edit click)\n var manifestCache = null;\n var manifestPromise = null;\n\n function fetchManifest() {\n if (manifestCache) return Promise.resolve(manifestCache);\n if (manifestPromise) return manifestPromise;\n manifestPromise = ecFetch(\"/_emdash/api/manifest\", { credentials: \"same-origin\" })\n .then(function(r) { return r.json(); })\n .then(function(m) {\n // The manifest endpoint wraps the payload in a { data } envelope (ApiResponse shape).\n // Unwrap it so getFieldKind can read manifest.collections directly.\n manifestCache = m && m.data ? m.data : m;\n return manifestCache;\n });\n return manifestPromise;\n }\n\n function getFieldKind(manifest, collection, field) {\n var col = manifest.collections && manifest.collections[collection];\n if (!col || !col.fields) return null;\n var f = col.fields[field];\n return f ? f.kind : null;\n }\n\n // Load manifest early so the first click can resolve field kinds without racing the event.\n if (isEditMode) {\n fetchManifest();\n }\n\n // Save a single field value\n function saveField(collection, id, field, value) {\n setSaveState(\"saving\");\n return ecFetch(\"/_emdash/api/content/\" + encodeURIComponent(collection) + \"/\" + encodeURIComponent(id), {\n method: \"PUT\",\n credentials: \"same-origin\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ data: { [field]: value } }),\n })\n .then(function(res) {\n if (res.ok) {\n setSaveState(\"saved\");\n // A save creates/updates a draft — show unpublished changes\n showUnpublishedChanges(collection, id);\n } else {\n setSaveState(\"error\");\n console.error(\"Save failed:\", res.status);\n }\n })\n .catch(function(err) {\n setSaveState(\"error\");\n console.error(\"Save failed:\", err);\n });\n }\n\n function showUnpublishedChanges(collection, id) {\n statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--pending\">Unpublished changes</span>';\n publishBtn.style.display = \"\";\n publishBtn.disabled = false;\n publishBtn.textContent = \"Publish\";\n publishBtn.onclick = function() { publish(collection, id); };\n }\n\n // Plain text inline editing (contenteditable)\n var currentlyEditing = null;\n\n function startTextEdit(element, annotation) {\n if (currentlyEditing === element) return;\n if (currentlyEditing) endCurrentEdit();\n\n currentlyEditing = element;\n var originalText = element.textContent || \"\";\n\n element.setAttribute(\"data-emdash-editing\", \"\");\n element.contentEditable = \"plaintext-only\";\n element.focus();\n\n // Select all text\n var range = document.createRange();\n range.selectNodeContents(element);\n var sel = window.getSelection();\n sel.removeAllRanges();\n sel.addRange(range);\n\n // Track dirty state via input events\n function handleInput() {\n var current = (element.textContent || \"\").trim();\n if (current !== originalText.trim()) {\n setSaveState(\"unsaved\");\n } else {\n setSaveState(\"idle\");\n }\n }\n\n function handleBlur() {\n element.removeEventListener(\"blur\", handleBlur);\n element.removeEventListener(\"keydown\", handleKeydown);\n element.removeEventListener(\"input\", handleInput);\n element.contentEditable = \"false\";\n element.removeAttribute(\"data-emdash-editing\");\n currentlyEditing = null;\n\n var newValue = (element.textContent || \"\").trim();\n if (newValue !== originalText.trim()) {\n pendingSavePromise = saveField(annotation.collection, annotation.id, annotation.field, newValue).then(function() {\n pendingSavePromise = null;\n }, function() {\n pendingSavePromise = null;\n });\n } else {\n setSaveState(\"idle\");\n }\n }\n\n function handleKeydown(e) {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n element.blur();\n }\n if (e.key === \"Escape\") {\n element.textContent = originalText;\n setSaveState(\"idle\");\n element.blur();\n }\n }\n\n element.addEventListener(\"input\", handleInput);\n element.addEventListener(\"blur\", handleBlur);\n element.addEventListener(\"keydown\", handleKeydown);\n }\n\n function endCurrentEdit() {\n if (currentlyEditing) {\n currentlyEditing.blur();\n }\n }\n\n // Fallback: open admin\n function openAdmin(annotation) {\n var url = \"/_emdash/admin/content/\" + encodeURIComponent(annotation.collection) + \"/\" + encodeURIComponent(annotation.id);\n if (annotation.field) {\n url += \"?field=\" + encodeURIComponent(annotation.field);\n }\n window.open(url, \"emdash-admin\");\n }\n\n // --- Inline image editing ---\n var activeImagePopover = null;\n\n function closeImagePopover() {\n if (activeImagePopover) {\n activeImagePopover.backdrop.remove();\n activeImagePopover.popover.remove();\n if (activeImagePopover.escapeHandler) {\n document.removeEventListener(\"keydown\", activeImagePopover.escapeHandler);\n }\n activeImagePopover = null;\n }\n }\n\n function startImageEdit(element, annotation) {\n closeImagePopover();\n\n // Find the current image value by fetching the entry\n var collection = annotation.collection;\n var id = annotation.id;\n var field = annotation.field;\n\n // Find img element inside the annotated container (or the element itself if it's an img)\n var imgEl = element.tagName === \"IMG\" ? element : element.querySelector(\"img\");\n\n // Fetch current field value from the content API\n ecFetch(\"/_emdash/api/content/\" + encodeURIComponent(collection) + \"/\" + encodeURIComponent(id), {\n credentials: \"same-origin\"\n })\n .then(function(r) { return r.json(); })\n .then(function(entry) {\n var currentValue = entry.data && entry.data[field];\n showImagePopover(element, imgEl, annotation, currentValue);\n })\n .catch(function() {\n // If fetch fails, still show popover with what we can infer from DOM\n showImagePopover(element, imgEl, annotation, null);\n });\n }\n\n function showImagePopover(element, imgEl, annotation, currentValue) {\n closeImagePopover();\n\n var collection = annotation.collection;\n var id = annotation.id;\n var field = annotation.field;\n\n // Position near the element\n var rect = element.getBoundingClientRect();\n var viewportH = window.innerHeight;\n var viewportW = window.innerWidth;\n\n // Create backdrop for click-outside-to-close\n var backdrop = document.createElement(\"div\");\n backdrop.className = \"emdash-img-popover-backdrop\";\n backdrop.addEventListener(\"click\", function(e) {\n if (e.target === backdrop) closeImagePopover();\n });\n\n // Create popover\n var popover = document.createElement(\"div\");\n popover.className = \"emdash-img-popover\";\n\n var currentSrc = currentValue ? (currentValue.previewUrl || currentValue.src) : (imgEl ? imgEl.src : null);\n var currentAlt = currentValue ? (currentValue.alt || \"\") : (imgEl ? (imgEl.alt || \"\") : \"\");\n\n // Build popover HTML\n var html = '';\n html += '<div class=\"emdash-img-popover-header\">';\n html += ' <span class=\"emdash-img-popover-title\">Image</span>';\n html += ' <button class=\"emdash-img-popover-close\" data-action=\"close\">&times;</button>';\n html += '</div>';\n html += '<div class=\"emdash-img-popover-body\" id=\"emdash-img-main\">';\n\n if (currentSrc) {\n html += '<img class=\"emdash-img-preview\" src=\"' + escapeAttr(currentSrc) + '\" alt=\"\" />';\n } else {\n html += '<div class=\"emdash-img-empty\">No image selected</div>';\n }\n\n html += '<div class=\"emdash-img-field\">';\n html += ' <label for=\"emdash-img-alt\">Alt text</label>';\n html += ' <input type=\"text\" id=\"emdash-img-alt\" value=\"' + escapeAttr(currentAlt) + '\" placeholder=\"Describe the image\" />';\n html += '</div>';\n\n html += '<div class=\"emdash-img-actions\">';\n html += ' <button class=\"emdash-img-btn emdash-img-btn--primary\" data-action=\"browse\">Replace</button>';\n html += ' <label class=\"emdash-img-btn\" style=\"cursor:pointer\">';\n html += ' Upload';\n html += ' <input type=\"file\" accept=\"image/*\" id=\"emdash-img-upload\" style=\"display:none\" />';\n html += ' </label>';\n if (currentSrc) {\n html += ' <button class=\"emdash-img-btn emdash-img-btn--danger\" data-action=\"remove\">Remove</button>';\n }\n html += '</div>';\n html += '</div>';\n\n popover.innerHTML = html;\n\n backdrop.appendChild(popover);\n document.body.appendChild(backdrop);\n\n // Position the popover\n positionPopover(popover, rect, viewportW, viewportH);\n\n // Escape key handler\n function handleEscape(e) {\n if (e.key === \"Escape\") {\n closeImagePopover();\n document.removeEventListener(\"keydown\", handleEscape);\n }\n }\n document.addEventListener(\"keydown\", handleEscape);\n\n activeImagePopover = {\n backdrop: backdrop,\n popover: popover,\n annotation: annotation,\n currentValue: currentValue,\n element: element,\n imgEl: imgEl,\n escapeHandler: handleEscape\n };\n\n // Event handlers\n popover.querySelector('[data-action=\"close\"]').addEventListener(\"click\", closeImagePopover);\n\n popover.querySelector('[data-action=\"browse\"]').addEventListener(\"click\", function() {\n showMediaBrowser(popover, annotation, currentValue, element, imgEl);\n });\n\n var uploadInput = popover.querySelector(\"#emdash-img-upload\");\n uploadInput.addEventListener(\"change\", function(e) {\n var file = e.target.files && e.target.files[0];\n if (file) handleImageUpload(file, popover, annotation, element, imgEl);\n });\n\n var removeBtn = popover.querySelector('[data-action=\"remove\"]');\n if (removeBtn) {\n removeBtn.addEventListener(\"click\", function() {\n saveField(collection, id, field, null).then(function() {\n if (imgEl) {\n imgEl.style.display = \"none\";\n }\n closeImagePopover();\n });\n });\n }\n\n // Save alt text on change (debounced)\n var altInput = popover.querySelector(\"#emdash-img-alt\");\n var altTimer = null;\n altInput.addEventListener(\"input\", function() {\n clearTimeout(altTimer);\n altTimer = setTimeout(function() {\n var newAlt = altInput.value;\n if (currentValue) {\n var updated = Object.assign({}, currentValue, { alt: newAlt });\n saveField(collection, id, field, updated);\n if (imgEl) imgEl.alt = newAlt;\n }\n }, 500);\n });\n\n // Handle drag and drop on the popover body\n var body = popover.querySelector(\".emdash-img-popover-body\");\n body.addEventListener(\"dragover\", function(e) {\n e.preventDefault();\n body.classList.add(\"emdash-img-drop\");\n });\n body.addEventListener(\"dragleave\", function() {\n body.classList.remove(\"emdash-img-drop\");\n });\n body.addEventListener(\"drop\", function(e) {\n e.preventDefault();\n body.classList.remove(\"emdash-img-drop\");\n var file = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];\n if (file && file.type.startsWith(\"image/\")) {\n handleImageUpload(file, popover, annotation, element, imgEl);\n }\n });\n }\n\n function positionPopover(popover, targetRect, viewportW, viewportH) {\n var popoverW = 320;\n var gap = 8;\n\n // Try to place to the right of the element\n var left = targetRect.right + gap;\n var top = targetRect.top;\n\n // If it overflows right, place to the left\n if (left + popoverW > viewportW - 16) {\n left = targetRect.left - popoverW - gap;\n }\n // If it still overflows (narrow viewport), center below\n if (left < 16) {\n left = Math.max(16, (viewportW - popoverW) / 2);\n top = targetRect.bottom + gap;\n }\n // Clamp vertically\n if (top + 400 > viewportH - 80) { // 80 for toolbar\n top = Math.max(16, viewportH - 480);\n }\n if (top < 16) top = 16;\n\n popover.style.left = left + \"px\";\n popover.style.top = top + \"px\";\n }\n\n function escapeAttr(str) {\n return String(str || \"\").replace(/&/g, \"&amp;\").replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n }\n\n function showMediaBrowser(popover, annotation, currentValue, element, imgEl) {\n var mainBody = popover.querySelector(\"#emdash-img-main\");\n if (mainBody) mainBody.style.display = \"none\";\n\n // Remove existing browser if any\n var existing = popover.querySelector(\".emdash-img-browser\");\n if (existing) existing.remove();\n\n var browser = document.createElement(\"div\");\n browser.className = \"emdash-img-browser\";\n\n browser.innerHTML = '<div class=\"emdash-img-browser-header\">' +\n '<span class=\"emdash-img-browser-title\">Media Library</span>' +\n '<button class=\"emdash-img-browser-back\">Back</button>' +\n '</div>' +\n '<div class=\"emdash-img-loading\">Loading\\u2026</div>';\n\n popover.appendChild(browser);\n\n browser.querySelector(\".emdash-img-browser-back\").addEventListener(\"click\", function() {\n browser.remove();\n if (mainBody) mainBody.style.display = \"\";\n });\n\n // Fetch media\n ecFetch(\"/_emdash/api/media?mimeType=image/&limit=30\", { credentials: \"same-origin\" })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n var items = data.items || [];\n var loadingEl = browser.querySelector(\".emdash-img-loading\");\n if (loadingEl) loadingEl.remove();\n\n if (items.length === 0) {\n var empty = document.createElement(\"div\");\n empty.className = \"emdash-img-loading\";\n empty.textContent = \"No images found\";\n browser.appendChild(empty);\n return;\n }\n\n var grid = document.createElement(\"div\");\n grid.className = \"emdash-img-grid\";\n\n items.forEach(function(item) {\n var thumb = document.createElement(\"div\");\n thumb.className = \"emdash-img-grid-item\";\n if (currentValue && currentValue.id === item.id) {\n thumb.classList.add(\"emdash-img-grid-item--selected\");\n }\n var thumbUrl = item.url || item.previewUrl || (\"/_emdash/api/media/file/\" + item.storageKey);\n thumb.innerHTML = '<img src=\"' + escapeAttr(thumbUrl) + '\" alt=\"' + escapeAttr(item.alt || item.filename || \"\") + '\" loading=\"lazy\" />';\n\n thumb.addEventListener(\"click\", function() {\n selectMediaItem(item, annotation, element, imgEl);\n });\n\n grid.appendChild(thumb);\n });\n\n browser.appendChild(grid);\n })\n .catch(function(err) {\n var loadingEl = browser.querySelector(\".emdash-img-loading\");\n if (loadingEl) loadingEl.textContent = \"Failed to load media\";\n console.error(\"Media fetch error:\", err);\n });\n }\n\n function selectMediaItem(item, annotation, element, imgEl) {\n var collection = annotation.collection;\n var id = annotation.id;\n var field = annotation.field;\n\n var isLocal = !item.provider || item.provider === \"local\";\n var itemUrl = item.url || item.previewUrl || (\"/_emdash/api/media/file/\" + item.storageKey);\n\n var newValue = {\n id: item.id,\n provider: item.provider || \"local\",\n src: isLocal ? itemUrl : undefined,\n previewUrl: isLocal ? undefined : itemUrl,\n alt: item.alt || \"\",\n width: item.width,\n height: item.height,\n meta: item.meta\n };\n\n // Clean undefined fields\n Object.keys(newValue).forEach(function(k) {\n if (newValue[k] === undefined) delete newValue[k];\n });\n\n saveField(collection, id, field, newValue).then(function() {\n // Update the image in the DOM\n if (imgEl) {\n imgEl.src = itemUrl;\n imgEl.alt = item.alt || \"\";\n imgEl.style.display = \"\";\n }\n closeImagePopover();\n });\n }\n\n function handleImageUpload(file, popover, annotation, element, imgEl) {\n var collection = annotation.collection;\n var id = annotation.id;\n var field = annotation.field;\n\n // Show uploading state\n var mainBody = popover.querySelector(\"#emdash-img-main\");\n var browserEl = popover.querySelector(\".emdash-img-browser\");\n if (browserEl) browserEl.remove();\n if (mainBody) {\n mainBody.innerHTML = '<div class=\"emdash-img-uploading\">' +\n '<span>Uploading ' + escapeAttr(file.name) + '\\u2026</span>' +\n '</div>';\n mainBody.style.display = \"\";\n }\n\n // Detect dimensions before upload\n var dimPromise = new Promise(function(resolve) {\n if (!file.type.startsWith(\"image/\")) return resolve({});\n var img = new Image();\n img.onload = function() {\n resolve({ width: img.naturalWidth, height: img.naturalHeight });\n URL.revokeObjectURL(img.src);\n };\n img.onerror = function() {\n resolve({});\n URL.revokeObjectURL(img.src);\n };\n img.src = URL.createObjectURL(file);\n });\n\n dimPromise.then(function(dims) {\n // Generate a thumbnail for large images to avoid OOM in server-side\n // blurhash generation on memory-constrained runtimes (Workers).\n // Thumbnail fits within a 64x64 box (scale by max dimension) so that\n // extreme aspect ratios don't explode into a huge canvas client-side.\n var thumbPromise;\n if (dims.width && dims.height && dims.width * dims.height * 4 > 32 * 1024 * 1024) {\n thumbPromise = new Promise(function(resolve) {\n try {\n var maxDim = Math.max(dims.width, dims.height);\n var scale = Math.min(1, 64 / maxDim);\n var thumbW = Math.max(1, Math.round(dims.width * scale));\n var thumbH = Math.max(1, Math.round(dims.height * scale));\n var canvas = document.createElement(\"canvas\");\n canvas.width = thumbW;\n canvas.height = thumbH;\n var ctx = canvas.getContext(\"2d\");\n if (ctx) {\n var img = new Image();\n img.onload = function() {\n try {\n ctx.drawImage(img, 0, 0, thumbW, thumbH);\n canvas.toBlob(function(blob) {\n URL.revokeObjectURL(img.src);\n resolve(blob);\n }, \"image/png\");\n } catch (e) {\n URL.revokeObjectURL(img.src);\n resolve(null);\n }\n };\n img.onerror = function() {\n URL.revokeObjectURL(img.src);\n resolve(null);\n };\n img.src = URL.createObjectURL(file);\n } else {\n resolve(null);\n }\n } catch (e) {\n resolve(null);\n }\n });\n } else {\n thumbPromise = Promise.resolve(null);\n }\n\n return thumbPromise.then(function(thumbnail) {\n var formData = new FormData();\n formData.append(\"file\", file);\n if (dims.width) formData.append(\"width\", String(dims.width));\n if (dims.height) formData.append(\"height\", String(dims.height));\n if (thumbnail) formData.append(\"thumbnail\", thumbnail, \"thumb.png\");\n\n return ecFetch(\"/_emdash/api/media\", {\n method: \"POST\",\n credentials: \"same-origin\",\n body: formData\n });\n });\n })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n if (!data.item) throw new Error(\"Upload failed\");\n var item = data.item;\n selectMediaItem(item, annotation, element, imgEl);\n })\n .catch(function(err) {\n console.error(\"Upload error:\", err);\n setSaveState(\"error\");\n closeImagePopover();\n });\n }\n\n // Click handler for edit mode\n if (isEditMode) {\n document.addEventListener(\"click\", function(e) {\n var target = e.target;\n\n // Don't intercept clicks on elements currently being edited\n if (target.hasAttribute && target.hasAttribute(\"data-emdash-editing\")) return;\n\n // Walk up to find annotated element\n while (target && target !== document.body) {\n if (target.hasAttribute && target.hasAttribute(\"data-emdash-editing\")) return;\n\n var ref = target.getAttribute && target.getAttribute(\"data-emdash-ref\");\n if (ref) {\n try {\n var annotation = JSON.parse(ref);\n\n // Entry-level annotation (no field) — keep walking for a field-level ancestor\n if (!annotation.field) {\n target = target.parentElement;\n continue;\n }\n\n function dispatchInline(kind) {\n closeImagePopover();\n // Portable Text is edited in-page by InlinePortableTextEditor — do not open admin\n if (kind === \"portableText\") {\n return;\n }\n e.preventDefault();\n e.stopPropagation();\n if (kind === \"string\" || kind === \"text\") {\n startTextEdit(target, annotation);\n } else if (kind === \"image\") {\n startImageEdit(target, annotation);\n } else {\n openAdmin(annotation);\n }\n }\n\n if (manifestCache) {\n dispatchInline(getFieldKind(manifestCache, annotation.collection, annotation.field));\n } else {\n fetchManifest().then(function(manifest) {\n dispatchInline(getFieldKind(manifest, annotation.collection, annotation.field));\n });\n }\n } catch (err) {\n console.error(\"Failed to parse emdash ref:\", err);\n }\n return;\n }\n target = target.parentElement;\n }\n }, true);\n }\n\n updateStatus();\n})();\n</script>\n`;\n}\n","/**\n * EmDash Request Context Middleware\n *\n * Sets up AsyncLocalStorage-based request context for query functions.\n * Skips ALS entirely for logged-out users with no CMS signals (fast path).\n *\n * Handles:\n * - Preview tokens: _preview query param with signed HMAC token\n * - Edit mode: emdash-edit-mode cookie (for visual editing)\n * - Toolbar injection: floating pill for authenticated editors\n */\n\nimport { defineMiddleware } from \"astro:middleware\";\n\nimport { resolveSecretsCached } from \"#config/secrets.js\";\n\nimport { verifyPreviewToken, parseContentId } from \"../../preview/tokens.js\";\nimport { getRequestContext, runWithContext } from \"../../request-context.js\";\nimport { renderToolbar } from \"../../visual-editing/toolbar.js\";\n\n/**\n * Inject toolbar HTML into a response if it's an HTML page.\n * Returns the original response if not HTML.\n */\nasync function injectToolbar(response: Response, toolbarHtml: string): Promise<Response> {\n\tconst contentType = response.headers.get(\"content-type\");\n\tif (!contentType?.includes(\"text/html\")) return response;\n\n\tconst html = await response.text();\n\tif (!html.includes(\"</body>\")) return new Response(html, response);\n\n\tconst injected = html.replace(\"</body>\", `${toolbarHtml}</body>`);\n\tconst result = new Response(injected, {\n\t\tstatus: response.status,\n\t\theaders: response.headers,\n\t});\n\t// Toolbar-injected HTML is session-specific (its presence reveals an active\n\t// editor session); it must never be stored in a shared CDN cache and served\n\t// to anonymous visitors. Mirrors the preview branch's guard (#1398).\n\tresult.headers.set(\"Cache-Control\", \"private, no-store\");\n\treturn result;\n}\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n\tconst { cookies, url } = context;\n\n\t// Skip /_emdash routes (admin has its own UI, no rendering context needed)\n\tif (url.pathname.startsWith(\"/_emdash\")) {\n\t\treturn next();\n\t}\n\n\t// Check for authenticated editor (role >= 30)\n\tconst { user } = context.locals;\n\tconst isEditor = !!user && user.role >= 30;\n\n\t// Playground mode: the playground middleware (from @emdash-cms/cloudflare) stashes\n\t// the per-session DO database on locals.__playgroundDb. We set it via ALS here\n\t// (same module instance as the loader) so getDb() picks it up correctly.\n\t//\n\t// `dbIsIsolated: true` tells schema-derived caches (manifest, taxonomy defs,\n\t// byline/term existence probes) to bypass module-scope memoization — each\n\t// playground session is its own database with its own schema, so a cached\n\t// value from another session would be wrong.\n\tconst playgroundDb = context.locals.__playgroundDb;\n\tif (playgroundDb) {\n\t\t// Check if playground user has toggled edit mode on\n\t\tconst hasEditCookie = cookies.get(\"emdash-edit-mode\")?.value === \"true\";\n\t\treturn runWithContext({ editMode: hasEditCookie, db: playgroundDb, dbIsIsolated: true }, () =>\n\t\t\tnext(),\n\t\t);\n\t}\n\n\t// Fast path: check for CMS signals before doing any work\n\tconst hasEditCookie = cookies.get(\"emdash-edit-mode\")?.value === \"true\";\n\tconst hasPreviewToken = url.searchParams.has(\"_preview\");\n\n\t// No CMS signals and not an editor → skip everything (zero overhead)\n\tif (!hasEditCookie && !hasPreviewToken && !isEditor) {\n\t\treturn next();\n\t}\n\n\t// Determine edit mode: cookie AND authenticated editor\n\tconst editMode = hasEditCookie && isEditor;\n\n\t// Read locale from Astro's i18n routing\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- Astro context includes currentLocale when i18n is configured\n\tconst locale = (context as { currentLocale?: string }).currentLocale;\n\n\t// Verify preview token if present.\n\t// The preview secret is resolved via `resolveSecretsCached`: env wins,\n\t// otherwise a DB-stored value is read (or generated on first need).\n\t// `emdash.db` is set by the runtime middleware which runs first; the\n\t// only path where it's missing is a runtime-init failure.\n\tlet preview: { collection: string; id: string } | undefined;\n\tif (hasPreviewToken) {\n\t\tconst db = context.locals.emdash?.db;\n\t\tif (db) {\n\t\t\tconst { previewSecret } = await resolveSecretsCached(db);\n\t\t\tconst result = await verifyPreviewToken({ url, secret: previewSecret });\n\t\t\tif (result.valid) {\n\t\t\t\tconst { collection, id } = parseContentId(result.payload.cid);\n\t\t\t\tpreview = { collection, id };\n\t\t\t}\n\t\t} else {\n\t\t\tconsole.warn(\n\t\t\t\t\"[emdash] Preview token present but EmDash runtime not initialized; preview disabled.\",\n\t\t\t);\n\t\t}\n\t}\n\n\t// If we have CMS signals, wrap in ALS context\n\tconst needsContext = hasEditCookie || hasPreviewToken;\n\n\tif (needsContext) {\n\t\t// Merge with any outer ALS context (e.g. the per-request D1 session db\n\t\t// set by the runtime middleware). `storage.run()` replaces the store\n\t\t// wholesale, so without the spread the outer `db` would be lost and\n\t\t// loaders would fall back to the singleton non-session dialect.\n\t\tconst parent = getRequestContext();\n\t\treturn runWithContext({ ...parent, editMode, preview, locale }, async () => {\n\t\t\tlet response = await next();\n\n\t\t\t// Preview responses must not be cached -- draft content could leak past token expiry.\n\t\t\t// Clone the response before modifying headers — the original may be immutable.\n\t\t\tif (preview) {\n\t\t\t\tresponse = new Response(response.body, response);\n\t\t\t\tresponse.headers.set(\"Cache-Control\", \"private, no-store\");\n\t\t\t}\n\n\t\t\t// Inject toolbar for authenticated editors\n\t\t\tif (isEditor) {\n\t\t\t\tconst toolbarHtml = renderToolbar({\n\t\t\t\t\teditMode,\n\t\t\t\t\tisPreview: !!preview,\n\t\t\t\t});\n\t\t\t\treturn injectToolbar(response, toolbarHtml);\n\t\t\t}\n\n\t\t\treturn response;\n\t\t});\n\t}\n\n\t// Editor without CMS signals — no ALS needed, but inject toolbar\n\tif (isEditor) {\n\t\tconst response = await next();\n\t\tconst toolbarHtml = renderToolbar({\n\t\t\teditMode: false,\n\t\t\tisPreview: false,\n\t\t});\n\t\treturn injectToolbar(response, toolbarHtml);\n\t}\n\n\treturn next();\n});\n\nexport default onRequest;\n"],"mappings":"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"}
1
+ {"version":3,"file":"request-context.mjs","names":[],"sources":["../../../src/visual-editing/toolbar-bootstrap.ts","../../../src/visual-editing/toolbar.ts","../../../src/astro/middleware/request-context.ts"],"sourcesContent":["/**\n * EmDash Toolbar Bootstrap (toolbar: \"client\")\n *\n * A tiny script injected into every public HTML response when the client\n * toolbar mode is enabled. It is identical for every visitor, so the HTML\n * stays fully cacheable by shared caches (Workers Cache, Cache Everything,\n * Fastly, Varnish, …).\n *\n * Behavior: if this browser has logged into the admin (non-secret\n * localStorage flag set by the admin SPA), render a small \"Edit\" pill.\n * Clicking it verifies the session server-side and reloads the page with the\n * `_edit` query param — that URL is always rendered fresh with the full\n * server-side toolbar. Logged-out browsers pay one localStorage read and\n * nothing else. See Discussion #1742.\n */\n\n/**\n * Non-secret localStorage flag set by the admin SPA when an editor session\n * exists in this browser. It only means \"a session may exist\" — the click\n * handler verifies the real session before entering edit mode. The literal\n * is duplicated in `@emdash-cms/admin` (Shell/Header), which cannot import\n * from core.\n */\nexport const EDITOR_FLAG_KEY = \"emdash-editor\";\n\n/**\n * localStorage flag set when the user dismisses the toolbar in this browser.\n * Cleared the next time an editor opens the admin. Also duplicated in\n * `@emdash-cms/admin`.\n */\nexport const TOOLBAR_DISMISSED_KEY = \"emdash-toolbar-dismissed\";\n\n/**\n * Query param that requests a fresh (never cached) editor render. Presence is\n * verified server-side: non-editors are redirected to the canonical URL.\n */\nexport const EDIT_PARAM = \"_edit\";\n\nexport function renderToolbarBootstrap(): string {\n\treturn `\n<!-- EmDash Toolbar Bootstrap -->\n<script>\n(function() {\n var flag, dismissed;\n try {\n flag = localStorage.getItem(\"${EDITOR_FLAG_KEY}\");\n dismissed = localStorage.getItem(\"${TOOLBAR_DISMISSED_KEY}\");\n } catch (e) {\n return;\n }\n if (!flag || dismissed) return;\n // The server toolbar is present on _edit / edit-mode / preview renders.\n if (document.getElementById(\"emdash-toolbar\")) return;\n\n var root = document.createElement(\"div\");\n root.id = \"emdash-toolbar-bootstrap\";\n root.style.cssText = \"position:fixed;bottom:16px;left:50%;transform:translateX(-50%);z-index:999999;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;line-height:1;-webkit-font-smoothing:antialiased;\";\n\n var inner = document.createElement(\"div\");\n inner.style.cssText = \"display:flex;align-items:center;gap:10px;padding:8px 16px;background:#1a1a1a;color:#e0e0e0;border-radius:999px;box-shadow:0 4px 24px rgba(0,0,0,0.3),0 0 0 1px rgba(255,255,255,0.08);white-space:nowrap;user-select:none;\";\n\n var logo = document.createElement(\"span\");\n logo.textContent = \"EmDash\";\n logo.style.cssText = \"font-weight:600;font-size:12px;letter-spacing:0.02em;color:#fff;opacity:0.7;\";\n\n var divider = document.createElement(\"span\");\n divider.style.cssText = \"width:1px;height:16px;background:rgba(255,255,255,0.15);\";\n\n var editBtn = document.createElement(\"button\");\n editBtn.textContent = \"Edit\";\n editBtn.style.cssText = \"padding:4px 12px;background:#3b82f6;color:#fff;border:none;border-radius:999px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;\";\n\n var closeBtn = document.createElement(\"button\");\n closeBtn.textContent = \"\\\\u00d7\";\n closeBtn.title = \"Hide toolbar\";\n closeBtn.setAttribute(\"aria-label\", \"Hide toolbar\");\n closeBtn.style.cssText = \"background:none;border:none;color:#666;cursor:pointer;font-size:16px;padding:0 2px;line-height:1;font-family:inherit;\";\n\n editBtn.addEventListener(\"click\", function() {\n editBtn.disabled = true;\n fetch(\"/_emdash/api/auth/me\", {\n credentials: \"same-origin\",\n headers: { \"X-EmDash-Request\": \"1\" }\n })\n .then(function(r) { return r.ok ? r.json() : null; })\n .then(function(body) {\n var user = body && body.data;\n if (user && user.role >= 30) {\n var u = new URL(location.href);\n u.searchParams.set(\"${EDIT_PARAM}\", \"1\");\n location.href = u.toString();\n } else if (user) {\n // Logged in but not an editor — the flag is stale for this browser.\n try { localStorage.removeItem(\"${EDITOR_FLAG_KEY}\"); } catch (e) {}\n root.remove();\n } else {\n // No session — go to the admin login page.\n location.href = \"/_emdash/admin/login\";\n }\n })\n .catch(function() {\n editBtn.disabled = false;\n });\n });\n\n closeBtn.addEventListener(\"click\", function() {\n try { localStorage.setItem(\"${TOOLBAR_DISMISSED_KEY}\", \"1\"); } catch (e) {}\n root.remove();\n });\n\n inner.appendChild(logo);\n inner.appendChild(divider);\n inner.appendChild(editBtn);\n inner.appendChild(closeBtn);\n root.appendChild(inner);\n document.body.appendChild(root);\n})();\n</script>\n`;\n}\n","/**\n * EmDash Visual Editing Toolbar\n *\n * A floating pill injected via middleware for authenticated editors.\n * Renders as a plain HTML string with inline styles and a <script> tag.\n * No dependencies — works on any page with a </body> tag.\n */\n\ninterface ToolbarConfig {\n\teditMode: boolean;\n\tisPreview: boolean;\n}\n\nexport function renderToolbar(config: ToolbarConfig): string {\n\tconst { editMode, isPreview } = config;\n\n\treturn `\n<!-- EmDash Visual Editing Toolbar -->\n<div id=\"emdash-toolbar\" data-edit-mode=\"${editMode}\" data-preview=\"${isPreview}\">\n <div class=\"emdash-tb-inner\">\n <span class=\"emdash-tb-logo\">EmDash</span>\n\n <div class=\"emdash-tb-divider\"></div>\n\n <label class=\"emdash-tb-toggle\" title=\"Toggle edit mode\">\n <input type=\"checkbox\" id=\"emdash-edit-toggle\" ${editMode ? \"checked\" : \"\"} />\n <span class=\"emdash-tb-toggle-track\">\n <span class=\"emdash-tb-toggle-thumb\"></span>\n </span>\n <span class=\"emdash-tb-toggle-label\">Edit</span>\n </label>\n\n <span class=\"emdash-tb-status\" id=\"emdash-tb-status\"></span>\n\n <span class=\"emdash-tb-save-status\" id=\"emdash-tb-save-status\"></span>\n\n <a class=\"emdash-tb-admin\" id=\"emdash-tb-admin\" href=\"#\" target=\"emdash-admin\" style=\"display:none\" title=\"Open in admin\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/><polyline points=\"15 3 21 3 21 9\"/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"/></svg>\n </a>\n\n <button class=\"emdash-tb-publish\" id=\"emdash-tb-publish\" style=\"display:none\">Publish</button>\n\n <button class=\"emdash-tb-dismiss\" id=\"emdash-tb-dismiss\" title=\"Hide toolbar\" aria-label=\"Hide toolbar\">&times;</button>\n </div>\n</div>\n\n<style>\n #emdash-toolbar {\n position: fixed;\n bottom: 16px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 999999;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n font-size: 13px;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n }\n\n .emdash-tb-inner {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 16px;\n background: #1a1a1a;\n color: #e0e0e0;\n border-radius: 999px;\n box-shadow: 0 4px 24px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.08);\n white-space: nowrap;\n user-select: none;\n }\n\n .emdash-tb-logo {\n font-weight: 600;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: #fff;\n opacity: 0.7;\n }\n\n .emdash-tb-divider {\n width: 1px;\n height: 16px;\n background: rgba(255,255,255,0.15);\n }\n\n /* Toggle switch */\n .emdash-tb-toggle {\n display: flex;\n align-items: center;\n gap: 6px;\n cursor: pointer;\n }\n\n .emdash-tb-toggle input {\n position: absolute;\n opacity: 0;\n width: 0;\n height: 0;\n }\n\n .emdash-tb-toggle-track {\n position: relative;\n width: 32px;\n height: 18px;\n background: #444;\n border-radius: 9px;\n transition: background 0.2s;\n }\n\n .emdash-tb-toggle input:checked + .emdash-tb-toggle-track {\n background: #3b82f6;\n }\n\n .emdash-tb-toggle-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 14px;\n height: 14px;\n background: #fff;\n border-radius: 50%;\n transition: transform 0.2s;\n }\n\n .emdash-tb-toggle input:checked + .emdash-tb-toggle-track .emdash-tb-toggle-thumb {\n transform: translateX(14px);\n }\n\n .emdash-tb-toggle-label {\n font-size: 12px;\n color: #aaa;\n }\n\n .emdash-tb-toggle input:checked ~ .emdash-tb-toggle-label {\n color: #fff;\n }\n\n /* Status area — flex for multiple badges */\n .emdash-tb-status {\n display: inline-flex;\n gap: 6px;\n align-items: center;\n }\n\n /* Badges */\n .emdash-tb-badge {\n display: inline-flex;\n align-items: center;\n padding: 3px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n }\n\n .emdash-tb-badge--preview {\n background: rgba(139,92,246,0.2);\n color: #a78bfa;\n }\n\n .emdash-tb-badge--draft {\n background: rgba(245,158,11,0.2);\n color: #fbbf24;\n }\n\n .emdash-tb-badge--published {\n background: rgba(34,197,94,0.2);\n color: #4ade80;\n }\n\n .emdash-tb-badge--pending {\n background: rgba(59,130,246,0.2);\n color: #60a5fa;\n }\n\n .emdash-tb-badge--unsaved {\n background: rgba(245,158,11,0.2);\n color: #fbbf24;\n }\n\n .emdash-tb-badge--saving {\n background: rgba(148,163,184,0.2);\n color: #94a3b8;\n }\n\n .emdash-tb-badge--saved {\n background: rgba(34,197,94,0.2);\n color: #4ade80;\n transition: opacity 0.3s;\n }\n\n .emdash-tb-badge--error {\n background: rgba(239,68,68,0.2);\n color: #f87171;\n }\n\n /* Admin link */\n .emdash-tb-admin {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: #888;\n text-decoration: none;\n padding: 2px;\n border-radius: 4px;\n transition: color 0.15s;\n }\n\n .emdash-tb-admin:hover {\n color: #fff;\n }\n\n /* Publish button */\n .emdash-tb-publish {\n padding: 4px 12px;\n background: #3b82f6;\n color: #fff;\n border: none;\n border-radius: 999px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background 0.15s;\n font-family: inherit;\n }\n\n .emdash-tb-publish:hover {\n background: #2563eb;\n }\n\n .emdash-tb-publish:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n /* Dismiss button */\n .emdash-tb-dismiss {\n background: none;\n border: none;\n color: #666;\n cursor: pointer;\n font-size: 16px;\n line-height: 1;\n padding: 0 2px;\n font-family: inherit;\n transition: color 0.15s;\n }\n\n .emdash-tb-dismiss:hover {\n color: #fff;\n }\n\n /* Edit mode: editable hover styles — uses :has() to check toolbar state */\n body:has(#emdash-toolbar[data-edit-mode=\"true\"]) [data-emdash-ref] {\n transition: box-shadow 0.15s, background-color 0.15s;\n }\n\n body:has(#emdash-toolbar[data-edit-mode=\"true\"]) [data-emdash-ref]:hover {\n box-shadow: 0 0 0 2px rgba(59,130,246,0.5);\n border-radius: 4px;\n background-color: rgba(59,130,246,0.04);\n cursor: text;\n }\n\n /* Active editing state — override hover pencil cursor */\n [data-emdash-editing] {\n box-shadow: 0 0 0 2px #3b82f6 !important;\n border-radius: 4px !important;\n background-color: rgba(59,130,246,0.04) !important;\n cursor: text !important;\n }\n\n /* Suppress browser focus ring on contenteditable and tiptap editor */\n [data-emdash-editing]:focus,\n [data-emdash-ref] .tiptap:focus,\n [data-emdash-ref] .ProseMirror:focus {\n outline: none !important;\n }\n\n /* Image editor popover */\n .emdash-img-popover {\n position: fixed;\n z-index: 1000000;\n background: #1a1a1a;\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.08);\n color: #e0e0e0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n font-size: 13px;\n width: 320px;\n overflow: hidden;\n animation: emdash-img-fadein 0.15s ease-out;\n }\n\n @keyframes emdash-img-fadein {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n .emdash-img-popover-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n border-bottom: 1px solid rgba(255,255,255,0.08);\n }\n\n .emdash-img-popover-title {\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: #999;\n }\n\n .emdash-img-popover-close {\n background: none;\n border: none;\n color: #666;\n cursor: pointer;\n padding: 2px;\n line-height: 1;\n font-size: 16px;\n border-radius: 4px;\n transition: color 0.15s;\n }\n\n .emdash-img-popover-close:hover {\n color: #fff;\n }\n\n .emdash-img-popover-body {\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .emdash-img-preview {\n width: 100%;\n max-height: 160px;\n object-fit: contain;\n border-radius: 6px;\n background: #111;\n }\n\n .emdash-img-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 80px;\n border: 2px dashed rgba(255,255,255,0.15);\n border-radius: 6px;\n color: #666;\n font-size: 12px;\n }\n\n .emdash-img-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .emdash-img-field label {\n font-size: 11px;\n font-weight: 600;\n color: #888;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n }\n\n .emdash-img-field input[type=\"text\"] {\n background: #111;\n border: 1px solid rgba(255,255,255,0.12);\n border-radius: 6px;\n color: #e0e0e0;\n padding: 6px 8px;\n font-size: 13px;\n font-family: inherit;\n outline: none;\n transition: border-color 0.15s;\n }\n\n .emdash-img-field input[type=\"text\"]:focus {\n border-color: #3b82f6;\n }\n\n .emdash-img-actions {\n display: flex;\n gap: 6px;\n }\n\n .emdash-img-btn {\n flex: 1;\n padding: 6px 10px;\n border: 1px solid rgba(255,255,255,0.12);\n border-radius: 6px;\n background: #222;\n color: #e0e0e0;\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n text-align: center;\n white-space: nowrap;\n }\n\n .emdash-img-btn:hover {\n background: #333;\n border-color: rgba(255,255,255,0.2);\n }\n\n .emdash-img-btn--primary {\n background: #3b82f6;\n border-color: #3b82f6;\n color: #fff;\n }\n\n .emdash-img-btn--primary:hover {\n background: #2563eb;\n border-color: #2563eb;\n }\n\n .emdash-img-btn--danger {\n color: #f87171;\n border-color: rgba(248,113,113,0.3);\n }\n\n .emdash-img-btn--danger:hover {\n background: rgba(248,113,113,0.1);\n border-color: rgba(248,113,113,0.5);\n }\n\n /* Media browser within the popover */\n .emdash-img-browser {\n border-top: 1px solid rgba(255,255,255,0.08);\n padding: 12px;\n }\n\n .emdash-img-browser-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 8px;\n }\n\n .emdash-img-browser-title {\n font-size: 12px;\n font-weight: 600;\n color: #999;\n }\n\n .emdash-img-browser-back {\n background: none;\n border: none;\n color: #3b82f6;\n cursor: pointer;\n font-size: 12px;\n font-family: inherit;\n padding: 2px 4px;\n }\n\n .emdash-img-browser-back:hover {\n text-decoration: underline;\n }\n\n .emdash-img-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 6px;\n max-height: 240px;\n overflow-y: auto;\n }\n\n .emdash-img-grid-item {\n aspect-ratio: 1;\n border-radius: 4px;\n overflow: hidden;\n cursor: pointer;\n border: 2px solid transparent;\n transition: border-color 0.15s;\n background: #111;\n }\n\n .emdash-img-grid-item:hover {\n border-color: rgba(59,130,246,0.5);\n }\n\n .emdash-img-grid-item--selected {\n border-color: #3b82f6;\n }\n\n .emdash-img-grid-item img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n .emdash-img-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 80px;\n color: #666;\n font-size: 12px;\n }\n\n .emdash-img-drop {\n border: 2px dashed #3b82f6;\n background: rgba(59,130,246,0.05);\n }\n\n .emdash-img-uploading {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 0;\n color: #999;\n font-size: 12px;\n }\n\n .emdash-img-popover-backdrop {\n position: fixed;\n inset: 0;\n z-index: 999999;\n }\n</style>\n\n<script>\n(function() {\n var toolbar = document.getElementById(\"emdash-toolbar\");\n var toggle = document.getElementById(\"emdash-edit-toggle\");\n var statusEl = document.getElementById(\"emdash-tb-status\");\n var saveStatusEl = document.getElementById(\"emdash-tb-save-status\");\n var publishBtn = document.getElementById(\"emdash-tb-publish\");\n if (!toolbar || !toggle || !statusEl || !publishBtn || !saveStatusEl) return;\n\n // Dismissed in this browser (localStorage flag, cleared on the next admin\n // visit). Remove the toolbar entirely instead of rendering it.\n try {\n if (localStorage.getItem(\"emdash-toolbar-dismissed\")) {\n toolbar.remove();\n return;\n }\n } catch (e) {\n // localStorage unavailable — render normally\n }\n\n var isEditMode = toolbar.getAttribute(\"data-edit-mode\") === \"true\";\n\n var dismissBtn = document.getElementById(\"emdash-tb-dismiss\");\n if (dismissBtn) {\n dismissBtn.addEventListener(\"click\", function() {\n try { localStorage.setItem(\"emdash-toolbar-dismissed\", \"1\"); } catch (e) {}\n // Dismissing while edit mode is on would strand the user in edit mode\n // with no UI to leave it — clear the cookie too.\n if (isEditMode) {\n document.cookie = \"emdash-edit-mode=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT\";\n toolbar.remove();\n location.replace(location.href);\n return;\n }\n toolbar.remove();\n });\n }\n\n // CSRF-protected fetch — adds X-EmDash-Request header to all API calls\n function ecFetch(url, init) {\n init = init || {};\n init.headers = Object.assign({ \"X-EmDash-Request\": \"1\" }, init.headers || {});\n return fetch(url, init);\n }\n\n // --- Save status tracking ---\n var saveState = \"idle\"; // idle | unsaved | saving | saved | error\n var saveHideTimer = null;\n var pendingSavePromise = null;\n\n function setSaveState(state) {\n saveState = state;\n clearTimeout(saveHideTimer);\n\n switch (state) {\n case \"unsaved\":\n saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--unsaved\">Unsaved</span>';\n break;\n case \"saving\":\n saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--saving\">Saving\\u2026</span>';\n break;\n case \"saved\":\n saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--saved\">Saved</span>';\n saveHideTimer = setTimeout(function() {\n saveStatusEl.innerHTML = \"\";\n saveState = \"idle\";\n }, 2000);\n break;\n case \"error\":\n saveStatusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--error\">Save failed</span>';\n saveHideTimer = setTimeout(function() {\n saveStatusEl.innerHTML = \"\";\n saveState = \"idle\";\n }, 3000);\n break;\n default:\n saveStatusEl.innerHTML = \"\";\n }\n }\n\n // Listen for save events from inline editors (e.g. PT editor)\n document.addEventListener(\"emdash:save\", function(e) {\n var detail = e.detail || {};\n if (detail.state) {\n setSaveState(detail.state);\n }\n });\n\n document.addEventListener(\"emdash:content-changed\", function(e) {\n var detail = e.detail || {};\n if (detail.collection && detail.id) {\n showUnpublishedChanges(detail.collection, detail.id);\n }\n });\n\n // --- Entry status ---\n var entryRef = null;\n\n function updateStatus() {\n if (!isEditMode) {\n statusEl.innerHTML = \"\";\n publishBtn.style.display = \"none\";\n return;\n }\n\n var first = document.querySelector(\"[data-emdash-ref]\");\n if (!first) {\n statusEl.innerHTML = \"\";\n publishBtn.style.display = \"none\";\n return;\n }\n\n try {\n var ref = JSON.parse(first.getAttribute(\"data-emdash-ref\"));\n entryRef = ref;\n if (!ref.status) return;\n\n // Show admin link\n var adminLink = document.getElementById(\"emdash-tb-admin\");\n if (adminLink) {\n adminLink.href = \"/_emdash/admin/content/\" + encodeURIComponent(ref.collection) + \"/\" + encodeURIComponent(ref.id);\n adminLink.style.display = \"\";\n }\n\n if (ref.status === \"draft\") {\n statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--draft\">Draft</span>';\n publishBtn.style.display = \"\";\n publishBtn.onclick = function() { publish(ref.collection, ref.id); };\n } else if (ref.status === \"published\" && ref.hasDraft) {\n statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--pending\">Unpublished changes</span>';\n publishBtn.style.display = \"\";\n publishBtn.onclick = function() { publish(ref.collection, ref.id); };\n } else if (ref.status === \"published\") {\n statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--published\">Published</span>';\n publishBtn.style.display = \"none\";\n }\n } catch (e) {\n // ignore parse errors\n }\n }\n\n // Publish action\n function publish(collection, id) {\n if (pendingSavePromise) {\n pendingSavePromise.then(function() { publish(collection, id); });\n return;\n }\n\n publishBtn.disabled = true;\n publishBtn.textContent = \"Publishing\\u2026\";\n\n ecFetch(\"/_emdash/api/content/\" + encodeURIComponent(collection) + \"/\" + encodeURIComponent(id) + \"/publish\", {\n method: \"POST\",\n credentials: \"same-origin\",\n })\n .then(function(res) {\n if (res.ok) {\n if (document.startViewTransition) {\n document.startViewTransition(function() { location.reload(); });\n } else {\n location.reload();\n }\n } else {\n publishBtn.disabled = false;\n publishBtn.textContent = \"Publish\";\n console.error(\"Publish failed:\", res.status);\n }\n })\n .catch(function(err) {\n publishBtn.disabled = false;\n publishBtn.textContent = \"Publish\";\n console.error(\"Publish failed:\", err);\n });\n }\n\n // Edit mode toggle\n toggle.addEventListener(\"change\", function() {\n if (toggle.checked) {\n document.cookie = \"emdash-edit-mode=true;path=/;samesite=lax\";\n } else {\n document.cookie = \"emdash-edit-mode=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT\";\n }\n\n if (document.startViewTransition) {\n document.startViewTransition(function() { location.replace(location.href); });\n } else {\n location.replace(location.href);\n }\n });\n\n // --- Inline editing ---\n\n // Cached manifest (fetched once on first edit click)\n var manifestCache = null;\n var manifestPromise = null;\n\n function fetchManifest() {\n if (manifestCache) return Promise.resolve(manifestCache);\n if (manifestPromise) return manifestPromise;\n manifestPromise = ecFetch(\"/_emdash/api/manifest\", { credentials: \"same-origin\" })\n .then(function(r) { return r.json(); })\n .then(function(m) {\n // The manifest endpoint wraps the payload in a { data } envelope (ApiResponse shape).\n // Unwrap it so getFieldKind can read manifest.collections directly.\n manifestCache = m && m.data ? m.data : m;\n return manifestCache;\n });\n return manifestPromise;\n }\n\n function getFieldKind(manifest, collection, field) {\n var col = manifest.collections && manifest.collections[collection];\n if (!col || !col.fields) return null;\n var f = col.fields[field];\n return f ? f.kind : null;\n }\n\n // Load manifest early so the first click can resolve field kinds without racing the event.\n if (isEditMode) {\n fetchManifest();\n }\n\n // Save a single field value\n function saveField(collection, id, field, value) {\n setSaveState(\"saving\");\n return ecFetch(\"/_emdash/api/content/\" + encodeURIComponent(collection) + \"/\" + encodeURIComponent(id), {\n method: \"PUT\",\n credentials: \"same-origin\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ data: { [field]: value } }),\n })\n .then(function(res) {\n if (res.ok) {\n setSaveState(\"saved\");\n // A save creates/updates a draft — show unpublished changes\n showUnpublishedChanges(collection, id);\n } else {\n setSaveState(\"error\");\n console.error(\"Save failed:\", res.status);\n }\n })\n .catch(function(err) {\n setSaveState(\"error\");\n console.error(\"Save failed:\", err);\n });\n }\n\n function showUnpublishedChanges(collection, id) {\n statusEl.innerHTML = '<span class=\"emdash-tb-badge emdash-tb-badge--pending\">Unpublished changes</span>';\n publishBtn.style.display = \"\";\n publishBtn.disabled = false;\n publishBtn.textContent = \"Publish\";\n publishBtn.onclick = function() { publish(collection, id); };\n }\n\n // Plain text inline editing (contenteditable)\n var currentlyEditing = null;\n\n function startTextEdit(element, annotation) {\n if (currentlyEditing === element) return;\n if (currentlyEditing) endCurrentEdit();\n\n currentlyEditing = element;\n var originalText = element.textContent || \"\";\n\n element.setAttribute(\"data-emdash-editing\", \"\");\n element.contentEditable = \"plaintext-only\";\n element.focus();\n\n // Select all text\n var range = document.createRange();\n range.selectNodeContents(element);\n var sel = window.getSelection();\n sel.removeAllRanges();\n sel.addRange(range);\n\n // Track dirty state via input events\n function handleInput() {\n var current = (element.textContent || \"\").trim();\n if (current !== originalText.trim()) {\n setSaveState(\"unsaved\");\n } else {\n setSaveState(\"idle\");\n }\n }\n\n function handleBlur() {\n element.removeEventListener(\"blur\", handleBlur);\n element.removeEventListener(\"keydown\", handleKeydown);\n element.removeEventListener(\"input\", handleInput);\n element.contentEditable = \"false\";\n element.removeAttribute(\"data-emdash-editing\");\n currentlyEditing = null;\n\n var newValue = (element.textContent || \"\").trim();\n if (newValue !== originalText.trim()) {\n pendingSavePromise = saveField(annotation.collection, annotation.id, annotation.field, newValue).then(function() {\n pendingSavePromise = null;\n }, function() {\n pendingSavePromise = null;\n });\n } else {\n setSaveState(\"idle\");\n }\n }\n\n function handleKeydown(e) {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n element.blur();\n }\n if (e.key === \"Escape\") {\n element.textContent = originalText;\n setSaveState(\"idle\");\n element.blur();\n }\n }\n\n element.addEventListener(\"input\", handleInput);\n element.addEventListener(\"blur\", handleBlur);\n element.addEventListener(\"keydown\", handleKeydown);\n }\n\n function endCurrentEdit() {\n if (currentlyEditing) {\n currentlyEditing.blur();\n }\n }\n\n // Fallback: open admin\n function openAdmin(annotation) {\n var url = \"/_emdash/admin/content/\" + encodeURIComponent(annotation.collection) + \"/\" + encodeURIComponent(annotation.id);\n if (annotation.field) {\n url += \"?field=\" + encodeURIComponent(annotation.field);\n }\n window.open(url, \"emdash-admin\");\n }\n\n // --- Inline image editing ---\n var activeImagePopover = null;\n\n function closeImagePopover() {\n if (activeImagePopover) {\n activeImagePopover.backdrop.remove();\n activeImagePopover.popover.remove();\n if (activeImagePopover.escapeHandler) {\n document.removeEventListener(\"keydown\", activeImagePopover.escapeHandler);\n }\n activeImagePopover = null;\n }\n }\n\n function startImageEdit(element, annotation) {\n closeImagePopover();\n\n // Find the current image value by fetching the entry\n var collection = annotation.collection;\n var id = annotation.id;\n var field = annotation.field;\n\n // Find img element inside the annotated container (or the element itself if it's an img)\n var imgEl = element.tagName === \"IMG\" ? element : element.querySelector(\"img\");\n\n // Fetch current field value from the content API\n ecFetch(\"/_emdash/api/content/\" + encodeURIComponent(collection) + \"/\" + encodeURIComponent(id), {\n credentials: \"same-origin\"\n })\n .then(function(r) { return r.json(); })\n .then(function(entry) {\n var currentValue = entry.data && entry.data[field];\n showImagePopover(element, imgEl, annotation, currentValue);\n })\n .catch(function() {\n // If fetch fails, still show popover with what we can infer from DOM\n showImagePopover(element, imgEl, annotation, null);\n });\n }\n\n function showImagePopover(element, imgEl, annotation, currentValue) {\n closeImagePopover();\n\n var collection = annotation.collection;\n var id = annotation.id;\n var field = annotation.field;\n\n // Position near the element\n var rect = element.getBoundingClientRect();\n var viewportH = window.innerHeight;\n var viewportW = window.innerWidth;\n\n // Create backdrop for click-outside-to-close\n var backdrop = document.createElement(\"div\");\n backdrop.className = \"emdash-img-popover-backdrop\";\n backdrop.addEventListener(\"click\", function(e) {\n if (e.target === backdrop) closeImagePopover();\n });\n\n // Create popover\n var popover = document.createElement(\"div\");\n popover.className = \"emdash-img-popover\";\n\n var currentSrc = currentValue ? (currentValue.previewUrl || currentValue.src) : (imgEl ? imgEl.src : null);\n var currentAlt = currentValue ? (currentValue.alt || \"\") : (imgEl ? (imgEl.alt || \"\") : \"\");\n\n // Build popover HTML\n var html = '';\n html += '<div class=\"emdash-img-popover-header\">';\n html += ' <span class=\"emdash-img-popover-title\">Image</span>';\n html += ' <button class=\"emdash-img-popover-close\" data-action=\"close\">&times;</button>';\n html += '</div>';\n html += '<div class=\"emdash-img-popover-body\" id=\"emdash-img-main\">';\n\n if (currentSrc) {\n html += '<img class=\"emdash-img-preview\" src=\"' + escapeAttr(currentSrc) + '\" alt=\"\" />';\n } else {\n html += '<div class=\"emdash-img-empty\">No image selected</div>';\n }\n\n html += '<div class=\"emdash-img-field\">';\n html += ' <label for=\"emdash-img-alt\">Alt text</label>';\n html += ' <input type=\"text\" id=\"emdash-img-alt\" value=\"' + escapeAttr(currentAlt) + '\" placeholder=\"Describe the image\" />';\n html += '</div>';\n\n html += '<div class=\"emdash-img-actions\">';\n html += ' <button class=\"emdash-img-btn emdash-img-btn--primary\" data-action=\"browse\">Replace</button>';\n html += ' <label class=\"emdash-img-btn\" style=\"cursor:pointer\">';\n html += ' Upload';\n html += ' <input type=\"file\" accept=\"image/*\" id=\"emdash-img-upload\" style=\"display:none\" />';\n html += ' </label>';\n if (currentSrc) {\n html += ' <button class=\"emdash-img-btn emdash-img-btn--danger\" data-action=\"remove\">Remove</button>';\n }\n html += '</div>';\n html += '</div>';\n\n popover.innerHTML = html;\n\n backdrop.appendChild(popover);\n document.body.appendChild(backdrop);\n\n // Position the popover\n positionPopover(popover, rect, viewportW, viewportH);\n\n // Escape key handler\n function handleEscape(e) {\n if (e.key === \"Escape\") {\n closeImagePopover();\n document.removeEventListener(\"keydown\", handleEscape);\n }\n }\n document.addEventListener(\"keydown\", handleEscape);\n\n activeImagePopover = {\n backdrop: backdrop,\n popover: popover,\n annotation: annotation,\n currentValue: currentValue,\n element: element,\n imgEl: imgEl,\n escapeHandler: handleEscape\n };\n\n // Event handlers\n popover.querySelector('[data-action=\"close\"]').addEventListener(\"click\", closeImagePopover);\n\n popover.querySelector('[data-action=\"browse\"]').addEventListener(\"click\", function() {\n showMediaBrowser(popover, annotation, currentValue, element, imgEl);\n });\n\n var uploadInput = popover.querySelector(\"#emdash-img-upload\");\n uploadInput.addEventListener(\"change\", function(e) {\n var file = e.target.files && e.target.files[0];\n if (file) handleImageUpload(file, popover, annotation, element, imgEl);\n });\n\n var removeBtn = popover.querySelector('[data-action=\"remove\"]');\n if (removeBtn) {\n removeBtn.addEventListener(\"click\", function() {\n saveField(collection, id, field, null).then(function() {\n if (imgEl) {\n imgEl.style.display = \"none\";\n }\n closeImagePopover();\n });\n });\n }\n\n // Save alt text on change (debounced)\n var altInput = popover.querySelector(\"#emdash-img-alt\");\n var altTimer = null;\n altInput.addEventListener(\"input\", function() {\n clearTimeout(altTimer);\n altTimer = setTimeout(function() {\n var newAlt = altInput.value;\n if (currentValue) {\n var updated = Object.assign({}, currentValue, { alt: newAlt });\n saveField(collection, id, field, updated);\n if (imgEl) imgEl.alt = newAlt;\n }\n }, 500);\n });\n\n // Handle drag and drop on the popover body\n var body = popover.querySelector(\".emdash-img-popover-body\");\n body.addEventListener(\"dragover\", function(e) {\n e.preventDefault();\n body.classList.add(\"emdash-img-drop\");\n });\n body.addEventListener(\"dragleave\", function() {\n body.classList.remove(\"emdash-img-drop\");\n });\n body.addEventListener(\"drop\", function(e) {\n e.preventDefault();\n body.classList.remove(\"emdash-img-drop\");\n var file = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];\n if (file && file.type.startsWith(\"image/\")) {\n handleImageUpload(file, popover, annotation, element, imgEl);\n }\n });\n }\n\n function positionPopover(popover, targetRect, viewportW, viewportH) {\n var popoverW = 320;\n var gap = 8;\n\n // Try to place to the right of the element\n var left = targetRect.right + gap;\n var top = targetRect.top;\n\n // If it overflows right, place to the left\n if (left + popoverW > viewportW - 16) {\n left = targetRect.left - popoverW - gap;\n }\n // If it still overflows (narrow viewport), center below\n if (left < 16) {\n left = Math.max(16, (viewportW - popoverW) / 2);\n top = targetRect.bottom + gap;\n }\n // Clamp vertically\n if (top + 400 > viewportH - 80) { // 80 for toolbar\n top = Math.max(16, viewportH - 480);\n }\n if (top < 16) top = 16;\n\n popover.style.left = left + \"px\";\n popover.style.top = top + \"px\";\n }\n\n function escapeAttr(str) {\n return String(str || \"\").replace(/&/g, \"&amp;\").replace(/\"/g, \"&quot;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n }\n\n function showMediaBrowser(popover, annotation, currentValue, element, imgEl) {\n var mainBody = popover.querySelector(\"#emdash-img-main\");\n if (mainBody) mainBody.style.display = \"none\";\n\n // Remove existing browser if any\n var existing = popover.querySelector(\".emdash-img-browser\");\n if (existing) existing.remove();\n\n var browser = document.createElement(\"div\");\n browser.className = \"emdash-img-browser\";\n\n browser.innerHTML = '<div class=\"emdash-img-browser-header\">' +\n '<span class=\"emdash-img-browser-title\">Media Library</span>' +\n '<button class=\"emdash-img-browser-back\">Back</button>' +\n '</div>' +\n '<div class=\"emdash-img-loading\">Loading\\u2026</div>';\n\n popover.appendChild(browser);\n\n browser.querySelector(\".emdash-img-browser-back\").addEventListener(\"click\", function() {\n browser.remove();\n if (mainBody) mainBody.style.display = \"\";\n });\n\n // Fetch media\n ecFetch(\"/_emdash/api/media?mimeType=image/&limit=30\", { credentials: \"same-origin\" })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n var items = data.items || [];\n var loadingEl = browser.querySelector(\".emdash-img-loading\");\n if (loadingEl) loadingEl.remove();\n\n if (items.length === 0) {\n var empty = document.createElement(\"div\");\n empty.className = \"emdash-img-loading\";\n empty.textContent = \"No images found\";\n browser.appendChild(empty);\n return;\n }\n\n var grid = document.createElement(\"div\");\n grid.className = \"emdash-img-grid\";\n\n items.forEach(function(item) {\n var thumb = document.createElement(\"div\");\n thumb.className = \"emdash-img-grid-item\";\n if (currentValue && currentValue.id === item.id) {\n thumb.classList.add(\"emdash-img-grid-item--selected\");\n }\n var thumbUrl = item.url || item.previewUrl || (\"/_emdash/api/media/file/\" + item.storageKey);\n thumb.innerHTML = '<img src=\"' + escapeAttr(thumbUrl) + '\" alt=\"' + escapeAttr(item.alt || item.filename || \"\") + '\" loading=\"lazy\" />';\n\n thumb.addEventListener(\"click\", function() {\n selectMediaItem(item, annotation, element, imgEl);\n });\n\n grid.appendChild(thumb);\n });\n\n browser.appendChild(grid);\n })\n .catch(function(err) {\n var loadingEl = browser.querySelector(\".emdash-img-loading\");\n if (loadingEl) loadingEl.textContent = \"Failed to load media\";\n console.error(\"Media fetch error:\", err);\n });\n }\n\n function selectMediaItem(item, annotation, element, imgEl) {\n var collection = annotation.collection;\n var id = annotation.id;\n var field = annotation.field;\n\n var isLocal = !item.provider || item.provider === \"local\";\n var itemUrl = item.url || item.previewUrl || (\"/_emdash/api/media/file/\" + item.storageKey);\n\n var newValue = {\n id: item.id,\n provider: item.provider || \"local\",\n src: isLocal ? itemUrl : undefined,\n previewUrl: isLocal ? undefined : itemUrl,\n alt: item.alt || \"\",\n width: item.width,\n height: item.height,\n meta: item.meta\n };\n\n // Clean undefined fields\n Object.keys(newValue).forEach(function(k) {\n if (newValue[k] === undefined) delete newValue[k];\n });\n\n saveField(collection, id, field, newValue).then(function() {\n // Update the image in the DOM\n if (imgEl) {\n imgEl.src = itemUrl;\n imgEl.alt = item.alt || \"\";\n imgEl.style.display = \"\";\n }\n closeImagePopover();\n });\n }\n\n function handleImageUpload(file, popover, annotation, element, imgEl) {\n var collection = annotation.collection;\n var id = annotation.id;\n var field = annotation.field;\n\n // Show uploading state\n var mainBody = popover.querySelector(\"#emdash-img-main\");\n var browserEl = popover.querySelector(\".emdash-img-browser\");\n if (browserEl) browserEl.remove();\n if (mainBody) {\n mainBody.innerHTML = '<div class=\"emdash-img-uploading\">' +\n '<span>Uploading ' + escapeAttr(file.name) + '\\u2026</span>' +\n '</div>';\n mainBody.style.display = \"\";\n }\n\n // Detect dimensions before upload\n var dimPromise = new Promise(function(resolve) {\n if (!file.type.startsWith(\"image/\")) return resolve({});\n var img = new Image();\n img.onload = function() {\n resolve({ width: img.naturalWidth, height: img.naturalHeight });\n URL.revokeObjectURL(img.src);\n };\n img.onerror = function() {\n resolve({});\n URL.revokeObjectURL(img.src);\n };\n img.src = URL.createObjectURL(file);\n });\n\n dimPromise.then(function(dims) {\n // Generate a thumbnail for large images to avoid OOM in server-side\n // blurhash generation on memory-constrained runtimes (Workers).\n // Thumbnail fits within a 64x64 box (scale by max dimension) so that\n // extreme aspect ratios don't explode into a huge canvas client-side.\n var thumbPromise;\n if (dims.width && dims.height && dims.width * dims.height * 4 > 32 * 1024 * 1024) {\n thumbPromise = new Promise(function(resolve) {\n try {\n var maxDim = Math.max(dims.width, dims.height);\n var scale = Math.min(1, 64 / maxDim);\n var thumbW = Math.max(1, Math.round(dims.width * scale));\n var thumbH = Math.max(1, Math.round(dims.height * scale));\n var canvas = document.createElement(\"canvas\");\n canvas.width = thumbW;\n canvas.height = thumbH;\n var ctx = canvas.getContext(\"2d\");\n if (ctx) {\n var img = new Image();\n img.onload = function() {\n try {\n ctx.drawImage(img, 0, 0, thumbW, thumbH);\n canvas.toBlob(function(blob) {\n URL.revokeObjectURL(img.src);\n resolve(blob);\n }, \"image/png\");\n } catch (e) {\n URL.revokeObjectURL(img.src);\n resolve(null);\n }\n };\n img.onerror = function() {\n URL.revokeObjectURL(img.src);\n resolve(null);\n };\n img.src = URL.createObjectURL(file);\n } else {\n resolve(null);\n }\n } catch (e) {\n resolve(null);\n }\n });\n } else {\n thumbPromise = Promise.resolve(null);\n }\n\n return thumbPromise.then(function(thumbnail) {\n var formData = new FormData();\n formData.append(\"file\", file);\n if (dims.width) formData.append(\"width\", String(dims.width));\n if (dims.height) formData.append(\"height\", String(dims.height));\n if (thumbnail) formData.append(\"thumbnail\", thumbnail, \"thumb.png\");\n\n return ecFetch(\"/_emdash/api/media\", {\n method: \"POST\",\n credentials: \"same-origin\",\n body: formData\n });\n });\n })\n .then(function(r) { return r.json(); })\n .then(function(data) {\n if (!data.item) throw new Error(\"Upload failed\");\n var item = data.item;\n selectMediaItem(item, annotation, element, imgEl);\n })\n .catch(function(err) {\n console.error(\"Upload error:\", err);\n setSaveState(\"error\");\n closeImagePopover();\n });\n }\n\n // Click handler for edit mode\n if (isEditMode) {\n document.addEventListener(\"click\", function(e) {\n var target = e.target;\n\n // Don't intercept clicks on elements currently being edited\n if (target.hasAttribute && target.hasAttribute(\"data-emdash-editing\")) return;\n\n // Walk up to find annotated element\n while (target && target !== document.body) {\n if (target.hasAttribute && target.hasAttribute(\"data-emdash-editing\")) return;\n\n var ref = target.getAttribute && target.getAttribute(\"data-emdash-ref\");\n if (ref) {\n try {\n var annotation = JSON.parse(ref);\n\n // Entry-level annotation (no field) — keep walking for a field-level ancestor\n if (!annotation.field) {\n target = target.parentElement;\n continue;\n }\n\n function dispatchInline(kind) {\n closeImagePopover();\n // Portable Text is edited in-page by InlinePortableTextEditor — do not open admin\n if (kind === \"portableText\") {\n return;\n }\n e.preventDefault();\n e.stopPropagation();\n if (kind === \"string\" || kind === \"text\") {\n startTextEdit(target, annotation);\n } else if (kind === \"image\") {\n startImageEdit(target, annotation);\n } else {\n openAdmin(annotation);\n }\n }\n\n if (manifestCache) {\n dispatchInline(getFieldKind(manifestCache, annotation.collection, annotation.field));\n } else {\n fetchManifest().then(function(manifest) {\n dispatchInline(getFieldKind(manifest, annotation.collection, annotation.field));\n });\n }\n } catch (err) {\n console.error(\"Failed to parse emdash ref:\", err);\n }\n return;\n }\n target = target.parentElement;\n }\n }, true);\n }\n\n updateStatus();\n})();\n</script>\n`;\n}\n","/**\n * EmDash Request Context Middleware\n *\n * Sets up AsyncLocalStorage-based request context for query functions.\n * Skips ALS entirely for logged-out users with no CMS signals (fast path).\n *\n * Handles:\n * - Preview tokens: _preview query param with signed HMAC token\n * - Edit mode: emdash-edit-mode cookie (for visual editing)\n * - Toolbar injection: floating pill for authenticated editors\n * - Client toolbar mode (`toolbar: \"client\"`): cache-identical HTML with a\n * client-side bootstrap pill and an `_edit` query param for fresh editor\n * renders (Discussion #1742)\n */\n\nimport type { APIContext } from \"astro\";\nimport { defineMiddleware } from \"astro:middleware\";\n// @ts-ignore - virtual module\nimport virtualConfig from \"virtual:emdash/config\";\n\nimport { resolveSecretsCached } from \"#config/secrets.js\";\n\nimport { verifyPreviewToken, parseContentId } from \"../../preview/tokens.js\";\nimport { getRequestContext, runWithContext } from \"../../request-context.js\";\nimport { EDIT_PARAM, renderToolbarBootstrap } from \"../../visual-editing/toolbar-bootstrap.js\";\nimport { renderToolbar } from \"../../visual-editing/toolbar.js\";\n\ntype ToolbarMode = \"server\" | \"client\" | false;\n\nconst toolbarMode: ToolbarMode = virtualConfig?.toolbar ?? \"server\";\n\n/** Astro's route-cache handle. EmDash requires Astro 6+, so it's always present. */\ntype RouteCache = APIContext[\"cache\"];\n\n/**\n * Opt the current request out of Astro's route cache (e.g. Workers Cache on\n * Cloudflare). `Cache-Control` headers do NOT cover this: the adapter derives\n * the shared-cache TTL from the route-cache options (on Cloudflare via\n * `Cloudflare-CDN-Cache-Control`), so session-specific responses must\n * explicitly disable it or they get stored in the shared cache and served to\n * anonymous visitors without ever invoking the middleware again. With no cache\n * provider configured this is a no-op (`NoopAstroCache`/`DisabledAstroCache`).\n */\nfunction optOutOfRouteCache(cache: RouteCache): void {\n\tcache.set(false);\n}\n\n/**\n * Inject HTML before `</body>` if the response is an HTML page with a body\n * end tag. Does not touch cache headers — callers decide whether the result\n * is still shareable. `injected` tells the caller whether anything changed.\n */\nasync function injectBeforeBodyEnd(\n\tresponse: Response,\n\thtmlToInject: string,\n): Promise<{ response: Response; injected: boolean }> {\n\tconst contentType = response.headers.get(\"content-type\");\n\tif (!contentType?.includes(\"text/html\")) return { response, injected: false };\n\n\tconst html = await response.text();\n\tif (!html.includes(\"</body>\")) {\n\t\t// Body already consumed — rebuild the response unchanged.\n\t\treturn { response: new Response(html, response), injected: false };\n\t}\n\n\tconst injected = html.replace(\"</body>\", `${htmlToInject}</body>`);\n\treturn {\n\t\tresponse: new Response(injected, {\n\t\t\tstatus: response.status,\n\t\t\theaders: response.headers,\n\t\t}),\n\t\tinjected: true,\n\t};\n}\n\n/**\n * Inject toolbar HTML into a response if it's an HTML page.\n * Returns the original response if not HTML.\n */\nasync function injectToolbar(\n\tresponse: Response,\n\ttoolbarHtml: string,\n\trouteCache: RouteCache,\n): Promise<Response> {\n\tconst result = await injectBeforeBodyEnd(response, toolbarHtml);\n\tif (result.injected) {\n\t\t// Toolbar-injected HTML is session-specific (its presence reveals an\n\t\t// active editor session); it must never be stored in a shared CDN cache\n\t\t// and served to anonymous visitors. Mirrors the preview branch's guard\n\t\t// (#1398). `Cache-Control` covers browsers/downstream proxies; the\n\t\t// route-cache opt-out covers the shared edge cache, which ignores\n\t\t// `Cache-Control`.\n\t\tresult.response.headers.set(\"Cache-Control\", \"private, no-store\");\n\t\toptOutOfRouteCache(routeCache);\n\t}\n\treturn result.response;\n}\n\n/**\n * Inject the client-toolbar bootstrap script. Identical for every visitor, so\n * cache headers and route-cache options are left untouched and the response\n * stays fully shareable.\n */\nasync function injectBootstrap(response: Response): Promise<Response> {\n\tconst result = await injectBeforeBodyEnd(response, renderToolbarBootstrap());\n\treturn result.response;\n}\n\n/**\n * Redirect an `_edit` URL to its canonical form (same URL without the param).\n * Applied when the requester is not an authenticated editor, so a shared\n * `?_edit` link degrades gracefully for everyone else (Discussion #1742).\n */\nfunction redirectToCanonical(url: URL): Response {\n\tconst canonical = new URL(url);\n\tcanonical.searchParams.delete(EDIT_PARAM);\n\treturn new Response(null, {\n\t\tstatus: 302,\n\t\theaders: {\n\t\t\tLocation: canonical.pathname + canonical.search + canonical.hash,\n\t\t\t// Header-following caches (Fastly, Varnish, browsers) must not store\n\t\t\t// the redirect — a cached 302 would bounce editors back to the\n\t\t\t// canonical URL. The route-cache opt-out at the call site covers the\n\t\t\t// Workers Cache, which ignores Cache-Control.\n\t\t\t\"Cache-Control\": \"private, no-store\",\n\t\t},\n\t});\n}\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n\tconst { cookies, url } = context;\n\n\t// Skip /_emdash routes (admin has its own UI, no rendering context needed)\n\tif (url.pathname.startsWith(\"/_emdash\")) {\n\t\treturn next();\n\t}\n\n\t// Check for authenticated editor (role >= 30)\n\tconst { user } = context.locals;\n\tconst isEditor = !!user && user.role >= 30;\n\n\t// Playground mode: the playground middleware (from @emdash-cms/cloudflare) stashes\n\t// the per-session DO database on locals.__playgroundDb. We set it via ALS here\n\t// (same module instance as the loader) so getDb() picks it up correctly.\n\t//\n\t// `dbIsIsolated: true` tells schema-derived caches (manifest, taxonomy defs,\n\t// byline/term existence probes) to bypass module-scope memoization — each\n\t// playground session is its own database with its own schema, so a cached\n\t// value from another session would be wrong.\n\tconst playgroundDb = context.locals.__playgroundDb;\n\tif (playgroundDb) {\n\t\t// Check if playground user has toggled edit mode on\n\t\tconst hasEditCookie = cookies.get(\"emdash-edit-mode\")?.value === \"true\";\n\t\treturn runWithContext({ editMode: hasEditCookie, db: playgroundDb, dbIsIsolated: true }, () =>\n\t\t\tnext(),\n\t\t);\n\t}\n\n\t// Fast path: check for CMS signals before doing any work\n\tconst hasEditCookie = cookies.get(\"emdash-edit-mode\")?.value === \"true\";\n\tconst hasPreviewToken = url.searchParams.has(\"_preview\");\n\t// `_edit` requests a fresh (never cached) editor render; only meaningful in\n\t// client toolbar mode where public HTML is otherwise identical for everyone.\n\tconst hasEditParam = toolbarMode === \"client\" && url.searchParams.has(EDIT_PARAM);\n\n\tif (hasEditParam) {\n\t\t// `_edit` URLs are their own cache key. Never store them in the route\n\t\t// cache — a cached anonymous redirect would bounce editors back to the\n\t\t// canonical URL, and a cached editor render must never be shared.\n\t\toptOutOfRouteCache(context.cache);\n\n\t\t// A non-editor (anonymous, logged-out, or insufficient role) opening an\n\t\t// `_edit` URL is sent to the canonical URL — shared `?_edit` links\n\t\t// degrade gracefully and never prime a cache entry with page content.\n\t\tif (!isEditor) {\n\t\t\treturn redirectToCanonical(url);\n\t\t}\n\t}\n\n\t// No CMS signals and not an editor → skip everything (zero overhead in\n\t// server mode; client mode injects the identical-for-everyone bootstrap)\n\tif (!hasEditCookie && !hasPreviewToken && !isEditor) {\n\t\tif (toolbarMode === \"client\") {\n\t\t\treturn injectBootstrap(await next());\n\t\t}\n\t\treturn next();\n\t}\n\n\t// Determine edit mode: cookie AND authenticated editor\n\tconst editMode = hasEditCookie && isEditor;\n\n\t// Read locale from Astro's i18n routing\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- Astro context includes currentLocale when i18n is configured\n\tconst locale = (context as { currentLocale?: string }).currentLocale;\n\n\tconst routeCache = context.cache;\n\n\t// Verify preview token if present.\n\t// The preview secret is resolved via `resolveSecretsCached`: env wins,\n\t// otherwise a DB-stored value is read (or generated on first need).\n\t// `emdash.db` is set by the runtime middleware which runs first; the\n\t// only path where it's missing is a runtime-init failure.\n\tlet preview: { collection: string; id: string } | undefined;\n\tif (hasPreviewToken) {\n\t\tconst db = context.locals.emdash?.db;\n\t\tif (db) {\n\t\t\tconst { previewSecret } = await resolveSecretsCached(db);\n\t\t\tconst result = await verifyPreviewToken({ url, secret: previewSecret });\n\t\t\tif (result.valid) {\n\t\t\t\tconst { collection, id } = parseContentId(result.payload.cid);\n\t\t\t\tpreview = { collection, id };\n\t\t\t}\n\t\t} else {\n\t\t\tconsole.warn(\n\t\t\t\t\"[emdash] Preview token present but EmDash runtime not initialized; preview disabled.\",\n\t\t\t);\n\t\t}\n\t}\n\n\t// If we have CMS signals, wrap in ALS context\n\tconst needsContext = hasEditCookie || hasPreviewToken;\n\n\tif (needsContext) {\n\t\t// Merge with any outer ALS context (e.g. the per-request D1 session db\n\t\t// set by the runtime middleware). `storage.run()` replaces the store\n\t\t// wholesale, so without the spread the outer `db` would be lost and\n\t\t// loaders would fall back to the singleton non-session dialect.\n\t\tconst parent = getRequestContext();\n\t\treturn runWithContext({ ...parent, editMode, preview, locale }, async () => {\n\t\t\tlet response = await next();\n\n\t\t\t// Preview responses must not be cached -- draft content could leak past token expiry.\n\t\t\t// Clone the response before modifying headers — the original may be immutable.\n\t\t\t// `Cache-Control` only governs browsers/downstream proxies; the shared\n\t\t\t// edge cache follows the route-cache options, so opt out of those too —\n\t\t\t// otherwise the draft response is stored in the shared cache and served\n\t\t\t// on cache hits without token verification until TTL/purge. Opt out for\n\t\t\t// any request carrying a `_preview` param (valid or not): those URLs are\n\t\t\t// per-token, so cached copies are useless at best and drafts at worst.\n\t\t\tif (hasPreviewToken) {\n\t\t\t\toptOutOfRouteCache(routeCache);\n\t\t\t}\n\t\t\tif (preview) {\n\t\t\t\tresponse = new Response(response.body, response);\n\t\t\t\tresponse.headers.set(\"Cache-Control\", \"private, no-store\");\n\t\t\t}\n\n\t\t\t// Inject toolbar for authenticated editors. Preview and edit-mode\n\t\t\t// responses are session-specific (`private, no-store` + route-cache\n\t\t\t// opt-out) in every toolbar mode, so the server toolbar is safe to\n\t\t\t// inject here even in client mode.\n\t\t\tif (isEditor && toolbarMode !== false) {\n\t\t\t\tconst toolbarHtml = renderToolbar({\n\t\t\t\t\teditMode,\n\t\t\t\t\tisPreview: !!preview,\n\t\t\t\t});\n\t\t\t\treturn injectToolbar(response, toolbarHtml, routeCache);\n\t\t\t}\n\n\t\t\t// Stale edit cookie without a session (client mode): still serve the\n\t\t\t// shareable bootstrap variant.\n\t\t\tif (toolbarMode === \"client\" && !isEditor && !preview) {\n\t\t\t\treturn injectBootstrap(response);\n\t\t\t}\n\n\t\t\treturn response;\n\t\t});\n\t}\n\n\t// Editor without preview/edit-mode signals.\n\tif (isEditor) {\n\t\tif (toolbarMode === false) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// Client mode: without the `_edit` param the response must stay\n\t\t// byte-identical to the anonymous variant (plus the same bootstrap\n\t\t// script), so shared caches serve one entry for everyone. The bootstrap\n\t\t// pill is the editor's entry point into the fresh `_edit` render.\n\t\tif (toolbarMode === \"client\" && !hasEditParam) {\n\t\t\treturn injectBootstrap(await next());\n\t\t}\n\n\t\t// Server mode, or an `_edit` request in client mode: inject the full\n\t\t// toolbar (response becomes `private, no-store` and route-cache\n\t\t// opted out).\n\t\tconst response = await next();\n\t\tconst toolbarHtml = renderToolbar({\n\t\t\teditMode: false,\n\t\t\tisPreview: false,\n\t\t});\n\t\treturn injectToolbar(response, toolbarHtml, routeCache);\n\t}\n\n\treturn next();\n});\n\nexport default onRequest;\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as getAuthMode } from "../../mode-BM9QgEmt.mjs";
1
+ import { t as getAuthMode } from "../../mode-BVnhGZ2o.mjs";
2
2
  import { defineMiddleware } from "astro:middleware";
3
3
 
4
4
  //#region src/astro/middleware/setup.ts
@@ -1,5 +1,5 @@
1
- import "../types-_HFLRzTn.mjs";
2
- import { t as PublishedRef } from "../scheduled-publish-Drffrf2a.mjs";
1
+ import "../types-momGysZk.mjs";
2
+ import { t as PublishedRef } from "../scheduled-publish-DlJHGcuL.mjs";
3
3
  import * as astro from "astro";
4
4
 
5
5
  //#region src/astro/middleware.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/astro/middleware.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBAgRsB,iBAAA,CACrB,OAAA;EAAW,WAAA,IAAe,IAAA,EAAM,YAAA,OAAmB,OAAA;AAAA,IACjD,OAAA;EAAU,SAAA,EAAW,YAAA;AAAA;AAAA,cAoKX,SAAA,EA6ZX,KAAA,CA7ZoB,iBAAA"}
1
+ {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/astro/middleware.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBA0RsB,iBAAA,CACrB,OAAA;EAAW,WAAA,IAAe,IAAA,EAAM,YAAA,OAAmB,OAAA;AAAA,IACjD,OAAA;EAAU,SAAA,EAAW,YAAA;AAAA;AAAA,cAoKX,SAAA,EAgaX,KAAA,CAhaoB,iBAAA"}
@@ -1,79 +1,83 @@
1
- import { i as runMigrations, t as MIGRATION_RACE_WAIT_MS } from "../runner-K8XK101B.mjs";
1
+ import { a as runMigrations, n as MIGRATION_RACE_WAIT_MS, t as ConcurrentMigrationTimeoutError } from "../runner-jt65bF7F.mjs";
2
2
  import { t as validateIdentifier } from "../validate-VPnKoIzW.mjs";
3
- import { o as isSqlite } from "../dialect-helpers-DRI5pyY3.mjs";
3
+ import { s as isSqlite } from "../dialect-helpers-Cue7QQOR.mjs";
4
4
  import { i as setI18nConfig } from "../config-CVssduLe.mjs";
5
- import { _ as resolveExclusiveHooks, g as createHookPipeline, m as EmailPipeline, p as PluginRouteRegistry, t as getMenu, v as definePlugin } from "../menus-b2T4clbO.mjs";
5
+ import { _ as resolveExclusiveHooks, g as createHookPipeline, m as EmailPipeline, p as PluginRouteRegistry, t as getMenu, v as definePlugin } from "../menus-BtvH7Z7t.mjs";
6
6
  import { t as after } from "../after-B1IIdH3Y.mjs";
7
7
  import { createRequestMetrics, getRequestContext, runWithContext } from "../request-context.mjs";
8
8
  import "../object-cache-Bok5j2ae.mjs";
9
- import { r as RevisionRepository, t as ContentRepository } from "../content-DhgEth4k.mjs";
10
- import "../base64-CmWvODNW.mjs";
11
- import "../types-CuW0zWDA.mjs";
12
- import { t as MediaRepository } from "../media-BrXIaMFN.mjs";
13
- import "../user-BjAOvU3Z.mjs";
14
- import "../taxonomy-BxGxZZdu.mjs";
15
- import "../comment-CzeatwBV.mjs";
16
- import { t as OptionsRepository } from "../options-DTTML-Tx.mjs";
17
- import "../context-BRIpHARC.mjs";
18
- import "../menus-Cfivc2Ad.mjs";
19
- import "../redirect-IyfNirgS.mjs";
20
- import { i as setRequestCacheEntry, r as requestCached } from "../request-cache-KCNHp_RJ.mjs";
21
- import "../byline-registry-BOjqDOim.mjs";
22
- import { n as initWithLock, t as createInitLock } from "../init-lock-6b309ZrF.mjs";
23
- import "../field-defs-cache-WUI88LX5.mjs";
24
- import "../byline-Bm8ZgUP9.mjs";
25
- import { n as normalizeMediaValue } from "../normalize-CeglrBT9.mjs";
26
- import "../enrich-BQ0mxJRs.mjs";
27
- import "../seo-DXu2UQnC.mjs";
28
- import { n as isMissingTableError } from "../db-errors-CK46D-ly.mjs";
29
- import { $ as handleContentGet, B as handleMediaUpdate, G as handleContentAuthors, H as handleRevisionList, I as handleMediaCreate, J as handleContentCountTrashed, K as handleContentCompare, L as handleMediaDelete, Q as handleContentDuplicate, R as handleMediaGet, S as PluginStateRepository, U as handleRevisionRestore, V as handleRevisionGet, X as handleContentDelete, Y as handleContentCreate, Z as handleContentDiscardDraft, _ as loadBundleFromR2, at as handleContentRestore, ct as handleContentUnpublish, dt as validateRev, et as handleContentGetIncludingTrashed, it as handleContentPublish, lt as handleContentUnschedule, nt as handleContentListTrashed, ot as handleContentSchedule, q as handleContentCountScheduled, rt as handleContentPermanentDelete, s as normalizeRegistryConfig, st as handleContentTranslations, tt as handleContentList, ut as handleContentUpdate, z as handleMediaList } from "../api-DxFSorgk.mjs";
30
- import "../dashboard-EvEKF5en.mjs";
31
- import { n as hashString } from "../hash-DrvzIXcz.mjs";
32
- import { i as markContentMediaUsageCollectionStale, o as refreshContentMediaUsageAfterWrite, r as findNonTranslatableSiblingContentIds, t as deleteContentMediaUsage } from "../content-refresh-Br4gMzh8.mjs";
33
- import { t as FTSManager } from "../fts-manager-BbFaF_V8.mjs";
34
- import { n as SchemaRegistry } from "../registry-B57vp_X9.mjs";
9
+ import { r as RevisionRepository, t as ContentRepository } from "../content-BO61Sx4j.mjs";
10
+ import { n as isMissingTableError } from "../db-errors-BKuXh_fi.mjs";
11
+ import "../base64-C74Msx8h.mjs";
12
+ import "../types-Ch8pFMEm.mjs";
13
+ import { t as MediaRepository } from "../media-D2ZFowpn.mjs";
14
+ import "../user-DTxX_Fha.mjs";
15
+ import "../taxonomy-BkxbUMSw.mjs";
16
+ import "../comment-SWh31kS0.mjs";
17
+ import { t as OptionsRepository } from "../options-BlmBHTvX.mjs";
18
+ import "../context-DtSFYFuS.mjs";
19
+ import "../menus-BpfevlJD.mjs";
20
+ import "../redirect-Ch3E98qZ.mjs";
21
+ import { i as setRequestCacheEntry, r as requestCached } from "../request-cache-BSUptuJR.mjs";
22
+ import "../byline-registry-BCuOp4UF.mjs";
23
+ import { n as initWithLock, t as createInitLock } from "../init-lock-DJkX6Hto.mjs";
24
+ import "../field-defs-cache-BVrNCgSj.mjs";
25
+ import "../byline-BWb5H-65.mjs";
26
+ import "../relation-Ba68Tm4Z.mjs";
27
+ import { n as normalizeMediaValue } from "../normalize-C-SHXmra.mjs";
28
+ import "../enrich-CFJJgxs_.mjs";
29
+ import "../seo-BLm6SXzS.mjs";
30
+ import { t as FTSManager } from "../fts-manager-BD9s77qh.mjs";
31
+ import "../resolve-Cd9dzclN.mjs";
35
32
  import { createRecorder, flushRecorder, isInstrumentationEnabled, kyselyLogOption } from "../database/instrumentation.mjs";
36
- import { a as getDb } from "../loader-CB6YHeul.mjs";
37
- import "../schema-CX9cfNkZ.mjs";
38
- import "../zod-generator-CacrR-i8.mjs";
39
- import "../seo-89-53DDs.mjs";
40
- import "../sections-CpF5SaGr.mjs";
41
- import { r as singleFlightCached, t as createSingleFlightCache } from "../single-flight-cache-BG3B_GhN.mjs";
42
- import { i as getSiteSettings, o as invalidateSiteSettingsCache } from "../settings-zhmdKyJQ.mjs";
43
- import "../settings-rZyHEcNT.mjs";
44
- import "../resolve-DR3wqMTy.mjs";
45
- import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-kH9ABDUF.mjs";
46
- import "../taxonomies-Bs13SN-F.mjs";
47
- import { r as normalizeManifestRoute } from "../manifest-schema-BHlgZM1W.mjs";
48
- import "../types-DROWRxYV.mjs";
49
- import "../ssrf-1g7usGBg.mjs";
50
- import "../error-CkAseSqZ.mjs";
51
- import "../parse-GmjecGct.mjs";
52
- import "../redirects-DXEXBAII.mjs";
53
- import "../byline-fields-2Lp5Hbgf.mjs";
54
- import "../status-CqJQX1QN.mjs";
55
- import { a as invalidateUrlPatternCache } from "../query-1Cp6U9oo.mjs";
56
- import "../import-fYLf14Co.mjs";
57
- import { t as getTrustedProxyHeaders } from "../trusted-proxy-VTPRiX2s.mjs";
58
- import { n as sanitizeHeadersForSandbox, t as extractRequestMeta } from "../request-meta-DbvXaAvU.mjs";
59
- import "../ssrf-CmuGgCq3.mjs";
60
- import { n as CronExecutor } from "../cron-v4FTEFR_.mjs";
61
- import { r as devConsoleEmailDeliver, t as DEV_CONSOLE_EMAIL_PLUGIN_ID } from "../email-console-CIf4Lfvv.mjs";
62
- import "../utils-BJXElxdw.mjs";
63
- import "../tokens-BGT0TUUo.mjs";
64
- import "../preview-SSXNVPAC.mjs";
65
- import "../comment-reaction-Cj9QJY7I.mjs";
66
- import "../bylines-CAZxGSrm.mjs";
67
- import { n as getWidgetAreas } from "../widgets-hWcIicX8.mjs";
68
- import "../apply-CDJqIjPU.mjs";
69
- import "../load-Djn3HWQS.mjs";
70
- import "../search-DZN2HZ-V.mjs";
33
+ import { a as getDb } from "../loader-Rb8QlGZp.mjs";
34
+ import { a as getTaxonomyDefs, o as getTaxonomyTerms } from "../taxonomies-pUkuD6nj.mjs";
35
+ import { $ as handleContentGet, B as handleMediaUpdate, G as handleContentAuthors, H as handleRevisionList, I as handleMediaCreate, J as handleContentCountTrashed, K as handleContentCompare, L as handleMediaDelete, Q as handleContentDuplicate, R as handleMediaGet, S as PluginStateRepository, U as handleRevisionRestore, V as handleRevisionGet, X as handleContentDelete, Y as handleContentCreate, Z as handleContentDiscardDraft, _ as loadBundleFromR2, at as handleContentRestore, ct as handleContentUnpublish, dt as validateRev, et as handleContentGetIncludingTrashed, it as handleContentPublish, lt as handleContentUnschedule, nt as handleContentListTrashed, ot as handleContentSchedule, q as handleContentCountScheduled, rt as handleContentPermanentDelete, s as normalizeRegistryConfig, st as handleContentTranslations, tt as handleContentList, ut as handleContentUpdate, z as handleMediaList } from "../api-DVbHNApo.mjs";
36
+ import "../dashboard-xo_gPs_D.mjs";
37
+ import { n as hashString } from "../hash-DFFrkivP.mjs";
38
+ import { a as findNonTranslatableSiblingContentIds, c as refreshContentMediaUsageAfterWrite, o as markContentMediaUsageCollectionStale, r as deleteContentMediaUsage } from "../content-refresh-BkkApzeS.mjs";
39
+ import "../media-usage-B__v9NMm.mjs";
40
+ import "../error-CfJMLSkd.mjs";
41
+ import { n as SchemaRegistry } from "../registry-CyIXPi4E.mjs";
42
+ import "../query-BxT95opY.mjs";
43
+ import "../zod-generator-CrfV79De.mjs";
44
+ import "../schema-DwjufZ1S.mjs";
45
+ import "../seo-BjrbXAd_.mjs";
46
+ import "../sections-DTKzgoiS.mjs";
47
+ import { r as singleFlightCached, t as createSingleFlightCache } from "../single-flight-cache-DEtW-OEG.mjs";
48
+ import { i as getSiteSettings, o as invalidateSiteSettingsCache } from "../settings-BuQYvLfy.mjs";
49
+ import "../settings-CmS3TwXf.mjs";
50
+ import "../taxonomies-ClS4RKaZ.mjs";
51
+ import { r as normalizeManifestRoute } from "../manifest-schema-DeGNma2O.mjs";
52
+ import "../types-DeCZBZxn.mjs";
53
+ import "../ssrf-TrKYJAy0.mjs";
54
+ import "../parse-GLksnn-B.mjs";
55
+ import "../redirects-CeD7K7EX.mjs";
56
+ import "../relations-BmBmX4Eb.mjs";
57
+ import "../status-D3Gy1Zc6.mjs";
58
+ import { a as invalidateUrlPatternCache } from "../query-ZzvXyxWq.mjs";
59
+ import "../import-2jHtLzsO.mjs";
60
+ import { t as getTrustedProxyHeaders } from "../trusted-proxy-CSgZVESw.mjs";
61
+ import { n as sanitizeHeadersForSandbox, t as extractRequestMeta } from "../request-meta-DCwktC5P.mjs";
62
+ import "../ssrf-B5DOv5E5.mjs";
63
+ import { n as CronExecutor } from "../cron-YFJ7uNVA.mjs";
64
+ import { r as devConsoleEmailDeliver, t as DEV_CONSOLE_EMAIL_PLUGIN_ID } from "../email-console-CjkQc7oY.mjs";
65
+ import "../utils-Cz8PVrl4.mjs";
66
+ import "../tokens-BV6fgl5A.mjs";
67
+ import "../preview-Rloa09Wp.mjs";
68
+ import "../hreflang-pAWUiPd_.mjs";
69
+ import "../comment-reaction-DgcpziXI.mjs";
70
+ import "../bylines-CHojDMpj.mjs";
71
+ import { n as getWidgetAreas } from "../widgets-B_X6_UXJ.mjs";
72
+ import "../apply-D23iUV4q.mjs";
73
+ import "../load-DgoWMdRX.mjs";
74
+ import "../search-Cl875CVE.mjs";
71
75
  import "../index.mjs";
72
- import { n as VERSION, t as COMMIT } from "../version-qmeO1XZY.mjs";
73
- import { t as getAuthMode } from "../mode-BM9QgEmt.mjs";
74
- import { t as cleanupExpiredChallenges } from "../challenge-store-anhwVER0.mjs";
75
- import { a as validateEncryptionKeyAtStartup } from "../secrets-kp760LlD.mjs";
76
- import { t as resolveSessionUser } from "../session-user-Cascbax7.mjs";
76
+ import { n as VERSION, t as COMMIT } from "../version-BlIJvNAZ.mjs";
77
+ import { t as getAuthMode } from "../mode-BVnhGZ2o.mjs";
78
+ import { t as cleanupExpiredChallenges } from "../challenge-store-B426wQ6O.mjs";
79
+ import { a as validateEncryptionKeyAtStartup } from "../secrets-CO_bEmII.mjs";
80
+ import { t as resolveSessionUser } from "../session-user-DoSs6YM8.mjs";
77
81
  import { Kysely, sql } from "kysely";
78
82
  import { defineMiddleware } from "astro:middleware";
79
83
  import virtualConfig from "virtual:emdash/config";
@@ -369,13 +373,22 @@ function getDbHolder() {
369
373
  if (!holder) {
370
374
  holder = {
371
375
  cache: /* @__PURE__ */ new Map(),
372
- lock: createInitLock()
376
+ lock: createInitLock(),
377
+ failures: /* @__PURE__ */ new Map()
373
378
  };
374
379
  globalSymbolStore[DB_HOLDER_KEY] = holder;
375
380
  }
381
+ holder.failures ??= /* @__PURE__ */ new Map();
376
382
  return holder;
377
383
  }
378
384
  /**
385
+ * After a database init fails (migrations threw), skip re-attempting for
386
+ * this long. Cold isolates still get one attempt each, so a transient
387
+ * failure heals on its own; a persistently failing migration is retried at
388
+ * most once per backoff window per isolate instead of on every request.
389
+ */
390
+ const DB_INIT_FAILURE_BACKOFF_MS = 3e4;
391
+ /**
379
392
  * Auto-seed runs at most once per isolate per database. Its lock + "done" set
380
393
  * live on globalThis (same bundler-duplication reasoning as the db cache) so a
381
394
  * reclaimed-and-rerun `create()` can't seed a second time concurrently. The
@@ -926,9 +939,9 @@ var EmDashRuntime = class EmDashRuntime {
926
939
  const seedHolder = getSeedHolder();
927
940
  try {
928
941
  await initWithLock(seedHolder.lock, () => seedHolder.done.has(seedKey) ? true : void 0, async () => {
929
- const { applySeed } = await import("../apply-CDJqIjPU.mjs").then((n) => n.n);
930
- const { loadSeed } = await import("../load-Djn3HWQS.mjs").then((n) => n.r);
931
- const { validateSeed } = await import("../validate-CWSnKVIJ.mjs").then((n) => n.n);
942
+ const { applySeed } = await import("../apply-D23iUV4q.mjs").then((n) => n.n);
943
+ const { loadSeed } = await import("../load-DgoWMdRX.mjs").then((n) => n.r);
944
+ const { validateSeed } = await import("../validate-C3CpWlNt.mjs").then((n) => n.n);
932
945
  const seed = await loadSeed();
933
946
  if (validateSeed(seed).valid) {
934
947
  await applySeed(db, seed, { onConflict: "skip" });
@@ -1132,12 +1145,30 @@ var EmDashRuntime = class EmDashRuntime {
1132
1145
  }
1133
1146
  const cacheKey = dbConfig.entrypoint;
1134
1147
  const holder = getDbHolder();
1148
+ const throwIfBackingOff = () => {
1149
+ const failure = holder.failures.get(cacheKey);
1150
+ if (!failure) return;
1151
+ if (Date.now() - failure.at < DB_INIT_FAILURE_BACKOFF_MS) throw new Error(`Database initialization is backing off after a recent migration failure: ${failure.message}`);
1152
+ holder.failures.delete(cacheKey);
1153
+ };
1154
+ throwIfBackingOff();
1135
1155
  return initWithLock(holder.lock, () => holder.cache.get(cacheKey), async (isCurrentClaim) => {
1156
+ throwIfBackingOff();
1136
1157
  const db = new Kysely({
1137
1158
  dialect: deps.createDialect(dbConfig.config),
1138
1159
  log: kyselyLogOption()
1139
1160
  });
1140
- await runMigrations(db);
1161
+ try {
1162
+ await runMigrations(db);
1163
+ } catch (error) {
1164
+ if (!(error instanceof ConcurrentMigrationTimeoutError)) holder.failures.set(cacheKey, {
1165
+ at: Date.now(),
1166
+ message: error instanceof Error ? error.message : String(error)
1167
+ });
1168
+ await db.destroy().catch(() => {});
1169
+ throw error;
1170
+ }
1171
+ holder.failures.delete(cacheKey);
1141
1172
  if (isCurrentClaim()) holder.cache.set(cacheKey, db);
1142
1173
  return db;
1143
1174
  }, {
@@ -1664,7 +1695,7 @@ var EmDashRuntime = class EmDashRuntime {
1664
1695
  if (this.hooks.hasHooks("content:beforeSave")) processedData = (await this.hooks.runContentBeforeSave(body.data, collection, true)).content;
1665
1696
  processedData = await this.runSandboxedBeforeSave(processedData, collection, true);
1666
1697
  processedData = await this.normalizeMediaFields(collection, processedData);
1667
- const { validateContentData } = await import("../validation-0l8-RYj4.mjs");
1698
+ const { validateContentData } = await import("../validation-DM2K_NJC.mjs");
1668
1699
  const validation = await validateContentData(this.db, collection, processedData, { partial: false });
1669
1700
  if (!validation.ok) return {
1670
1701
  success: false,
@@ -1681,7 +1712,7 @@ var EmDashRuntime = class EmDashRuntime {
1681
1712
  return result;
1682
1713
  }
1683
1714
  async handleContentUpdate(collection, id, body) {
1684
- const { ContentRepository } = await import("../content-DhgEth4k.mjs").then((n) => n.n);
1715
+ const { ContentRepository } = await import("../content-BO61Sx4j.mjs").then((n) => n.n);
1685
1716
  const repo = new ContentRepository(this.db);
1686
1717
  const resolvedItem = await repo.findByIdOrSlug(collection, id, body.locale);
1687
1718
  const resolvedId = resolvedItem?.id ?? id;
@@ -1708,7 +1739,7 @@ var EmDashRuntime = class EmDashRuntime {
1708
1739
  if (this.hooks.hasHooks("content:beforeSave")) processedData = (await this.hooks.runContentBeforeSave(bodyWithoutRev.data, collection, false)).content;
1709
1740
  processedData = await this.runSandboxedBeforeSave(processedData, collection, false);
1710
1741
  processedData = await this.normalizeMediaFields(collection, processedData);
1711
- const { validateContentData } = await import("../validation-0l8-RYj4.mjs");
1742
+ const { validateContentData } = await import("../validation-DM2K_NJC.mjs");
1712
1743
  const validation = await validateContentData(this.db, collection, processedData, { partial: true });
1713
1744
  if (!validation.ok) return {
1714
1745
  success: false,
@@ -2595,6 +2626,16 @@ function createPublicPluginApiRouteHandler(runtime) {
2595
2626
  */
2596
2627
  const RUNTIME_INIT_DEADLINE_MS = DB_INIT_DEADLINE_MS + 15e3;
2597
2628
  /**
2629
+ * Throttle for the anonymous-path runtime-init failure log. While a site is
2630
+ * stuck (e.g. a failing migration in its backoff window, #1744) every
2631
+ * anonymous request lands in that catch; one line per interval per isolate
2632
+ * keeps the failure visible without flooding logs on a busy site. Plain
2633
+ * module state (not globalThis): a duplicated SSR chunk just means an extra
2634
+ * log line, which is harmless.
2635
+ */
2636
+ const RUNTIME_INIT_ERROR_LOG_INTERVAL_MS = 3e4;
2637
+ let lastRuntimeInitErrorLogAt = 0;
2638
+ /**
2598
2639
  * Whether we've verified the database has been set up.
2599
2640
  * On a fresh deployment the first request may hit a public page, bypassing
2600
2641
  * runtime init. Without this check, template helpers like getSiteSettings()
@@ -2871,7 +2912,7 @@ const onRequest = defineMiddleware(async (context, next) => {
2871
2912
  if (!isSetupVerified() && !context.isPrerendered) {
2872
2913
  const t0 = performance.now();
2873
2914
  try {
2874
- const { getDb } = await import("../loader-CB6YHeul.mjs").then((n) => n.o);
2915
+ const { getDb } = await import("../loader-Rb8QlGZp.mjs").then((n) => n.o);
2875
2916
  await (await getDb()).selectFrom("_emdash_migrations").selectAll().limit(1).execute();
2876
2917
  markSetupVerified();
2877
2918
  } catch (error) {
@@ -2898,7 +2939,12 @@ const onRequest = defineMiddleware(async (context, next) => {
2898
2939
  getPublicMediaUrl: createPublicMediaUrlResolver(runtime.storage),
2899
2940
  storage: runtime.storage
2900
2941
  };
2901
- } catch {}
2942
+ } catch (error) {
2943
+ if (Date.now() - lastRuntimeInitErrorLogAt >= RUNTIME_INIT_ERROR_LOG_INTERVAL_MS) {
2944
+ lastRuntimeInitErrorLogAt = Date.now();
2945
+ console.error("[emdash] runtime init failed (page renders without CMS data):", error);
2946
+ }
2947
+ }
2902
2948
  timings.push({
2903
2949
  name: "rt",
2904
2950
  dur: performance.now() - t0,