emdash 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +11 -11
- package/dist/{api-D2_sbavn.mjs → api-BMfveMoB.mjs} +15 -15
- package/dist/{api-D2_sbavn.mjs.map → api-BMfveMoB.mjs.map} +1 -1
- package/dist/{apply-Bk2mAuLn.mjs → apply--IVNSdXn.mjs} +18 -16
- package/dist/apply--IVNSdXn.mjs.map +1 -0
- package/dist/astro/index.d.mts +7 -7
- package/dist/astro/index.mjs +2 -1
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +7 -7
- package/dist/astro/middleware/auth.mjs +2 -2
- package/dist/astro/middleware/redirect.mjs +4 -4
- package/dist/astro/middleware.d.mts +1 -1
- package/dist/astro/middleware.mjs +85 -85
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +3 -3
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +3 -3
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +2 -2
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +3 -3
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +3 -3
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +4 -4
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +10 -10
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +10 -10
- package/dist/astro/routes/api/admin/bylines/index.mjs +10 -10
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +8 -8
- package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -7
- package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/index.mjs +7 -7
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/index.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +29 -29
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +28 -28
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +29 -29
- package/dist/astro/routes/api/admin/plugins/updates.mjs +28 -28
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +28 -28
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +28 -28
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +3 -3
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
- package/dist/astro/routes/api/admin/users/index.mjs +3 -3
- package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/index.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/register-options.mjs +3 -3
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +4 -4
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/me.mjs +4 -4
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +4 -4
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/passkey/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/signup/complete.mjs +3 -3
- package/dist/astro/routes/api/auth/signup/request.mjs +4 -4
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +7 -7
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +7 -7
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/index.mjs +4 -4
- package/dist/astro/routes/api/content/_collection_/trash.mjs +4 -4
- package/dist/astro/routes/api/dashboard.mjs +8 -8
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/import/probe.d.mts +2 -2
- package/dist/astro/routes/api/import/probe.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +7 -7
- package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/media.d.mts +7 -7
- package/dist/astro/routes/api/import/wordpress/media.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +5 -5
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +4 -4
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +7 -7
- package/dist/astro/routes/api/manifest.mjs +3 -3
- package/dist/astro/routes/api/mcp.mjs +34 -34
- package/dist/astro/routes/api/media/_id_/confirm.mjs +4 -4
- package/dist/astro/routes/api/media/_id_.mjs +4 -4
- package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
- package/dist/astro/routes/api/media/providers/index.mjs +3 -3
- package/dist/astro/routes/api/media/upload-url.mjs +4 -4
- package/dist/astro/routes/api/media.mjs +5 -5
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -6
- package/dist/astro/routes/api/menus/_name_/items.mjs +6 -6
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -6
- package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -6
- package/dist/astro/routes/api/menus/_name_.mjs +6 -6
- package/dist/astro/routes/api/menus/index.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +3 -3
- package/dist/astro/routes/api/oauth/device/code.mjs +4 -4
- package/dist/astro/routes/api/oauth/device/token.mjs +4 -4
- package/dist/astro/routes/api/oauth/register.mjs +2 -2
- package/dist/astro/routes/api/oauth/token/refresh.mjs +3 -3
- package/dist/astro/routes/api/oauth/token/revoke.mjs +3 -3
- package/dist/astro/routes/api/oauth/token.mjs +2 -2
- package/dist/astro/routes/api/openapi.json.mjs +2 -2
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
- package/dist/astro/routes/api/redirects/404s/index.mjs +6 -6
- package/dist/astro/routes/api/redirects/404s/summary.mjs +6 -6
- package/dist/astro/routes/api/redirects/_id_.mjs +7 -7
- package/dist/astro/routes/api/redirects/index.mjs +7 -7
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +28 -28
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +28 -28
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +28 -28
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +28 -28
- package/dist/astro/routes/api/schema/collections/index.mjs +28 -28
- package/dist/astro/routes/api/schema/index.mjs +6 -6
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +28 -28
- package/dist/astro/routes/api/schema/orphans/index.mjs +28 -28
- package/dist/astro/routes/api/search/enable.mjs +7 -7
- package/dist/astro/routes/api/search/index.mjs +6 -6
- package/dist/astro/routes/api/search/rebuild.mjs +7 -7
- package/dist/astro/routes/api/search/stats.mjs +6 -6
- package/dist/astro/routes/api/search/suggest.mjs +6 -6
- package/dist/astro/routes/api/sections/_slug_.mjs +6 -6
- package/dist/astro/routes/api/sections/index.mjs +6 -6
- package/dist/astro/routes/api/settings/email.mjs +4 -4
- package/dist/astro/routes/api/settings.mjs +9 -9
- package/dist/astro/routes/api/setup/admin-verify.mjs +3 -3
- package/dist/astro/routes/api/setup/admin.mjs +3 -3
- package/dist/astro/routes/api/setup/dev-bypass.mjs +17 -17
- package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
- package/dist/astro/routes/api/setup/index.mjs +18 -18
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +3 -3
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +9 -9
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +9 -9
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +9 -9
- package/dist/astro/routes/api/taxonomies/index.mjs +9 -9
- package/dist/astro/routes/api/themes/preview.mjs +3 -3
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +4 -4
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
- package/dist/astro/routes/api/widget-areas/index.mjs +6 -6
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +5 -5
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +10 -9
- package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
- package/dist/astro/routes/sitemap.xml.mjs +5 -5
- package/dist/astro/types.d.mts +7 -7
- package/dist/{authorize-CuxZ0i2t.mjs → authorize-BZnOGJFo.mjs} +2 -2
- package/dist/{authorize-CuxZ0i2t.mjs.map → authorize-BZnOGJFo.mjs.map} +1 -1
- package/dist/{byline-C-YajfV7.mjs → byline-LL8lyqdb.mjs} +6 -6
- package/dist/{byline-C-YajfV7.mjs.map → byline-LL8lyqdb.mjs.map} +1 -1
- package/dist/{bylines-D4KADDAi.mjs → bylines-BmJ7Vg7J.mjs} +4 -4
- package/dist/{bylines-D4KADDAi.mjs.map → bylines-BmJ7Vg7J.mjs.map} +1 -1
- package/dist/{bylines-CPc_q2Vd.mjs → bylines-DE47vP8d.mjs} +5 -5
- package/dist/{bylines-CPc_q2Vd.mjs.map → bylines-DE47vP8d.mjs.map} +1 -1
- package/dist/{cache-C35VWD5D.mjs → cache-D9jF5bYw.mjs} +2 -2
- package/dist/{cache-C35VWD5D.mjs.map → cache-D9jF5bYw.mjs.map} +1 -1
- package/dist/{chunks-BHgcHh2C.mjs → chunks-OCG163rN.mjs} +2 -2
- package/dist/{chunks-BHgcHh2C.mjs.map → chunks-OCG163rN.mjs.map} +1 -1
- package/dist/cli/index.mjs +16 -16
- package/dist/{comment-BDx50EX5.mjs → comment-D9BLDpZI.mjs} +3 -3
- package/dist/{comment-BDx50EX5.mjs.map → comment-D9BLDpZI.mjs.map} +1 -1
- package/dist/{comment-reaction-Gv_pgfI3.mjs → comment-reaction-Drl9SkNu.mjs} +2 -2
- package/dist/{comment-reaction-Gv_pgfI3.mjs.map → comment-reaction-Drl9SkNu.mjs.map} +1 -1
- package/dist/{comments-CXHuQ6jI.mjs → comments-BjJ9SKGU.mjs} +3 -3
- package/dist/{comments-CXHuQ6jI.mjs.map → comments-BjJ9SKGU.mjs.map} +1 -1
- package/dist/{content-B43-YrIq.mjs → content-ncUWWjna.mjs} +4 -4
- package/dist/{content-B43-YrIq.mjs.map → content-ncUWWjna.mjs.map} +1 -1
- package/dist/{context-BW3N7wyt.mjs → context-C_g-iJVA.mjs} +7 -7
- package/dist/{context-BW3N7wyt.mjs.map → context-C_g-iJVA.mjs.map} +1 -1
- package/dist/{dashboard-FIe0S512.mjs → dashboard-CotyqQrW.mjs} +4 -4
- package/dist/{dashboard-FIe0S512.mjs.map → dashboard-CotyqQrW.mjs.map} +1 -1
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +1 -1
- package/dist/{error-DXnK2wF4.mjs → error-rVGiqWDt.mjs} +2 -2
- package/dist/{error-DXnK2wF4.mjs.map → error-rVGiqWDt.mjs.map} +1 -1
- package/dist/{field-defs-cache-CYYx8xlG.mjs → field-defs-cache-ljXzJCfc.mjs} +2 -2
- package/dist/{field-defs-cache-CYYx8xlG.mjs.map → field-defs-cache-ljXzJCfc.mjs.map} +1 -1
- package/dist/{fts-manager-DW_NR-Fs.mjs → fts-manager-BbCCbEKA.mjs} +2 -2
- package/dist/{fts-manager-DW_NR-Fs.mjs.map → fts-manager-BbCCbEKA.mjs.map} +1 -1
- package/dist/{index-Cz6_JBL3.d.mts → index-BRNabKrx.d.mts} +36 -11
- package/dist/index-BRNabKrx.d.mts.map +1 -0
- package/dist/{index-qbL02uhs.d.mts → index-Dt0OKJB4.d.mts} +2 -2
- package/dist/{index-qbL02uhs.d.mts.map → index-Dt0OKJB4.d.mts.map} +1 -1
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +38 -38
- package/dist/{load-DMFi4n1p.mjs → load-DMjXghbZ.mjs} +2 -2
- package/dist/{load-DMFi4n1p.mjs.map → load-DMjXghbZ.mjs.map} +1 -1
- package/dist/{loader-DrpofRon.mjs → loader-BA_zhkS_.mjs} +81 -18
- package/dist/loader-BA_zhkS_.mjs.map +1 -0
- package/dist/{manifest-schema-BOfPXse0.mjs → manifest-schema-kPGX7VS-.mjs} +3 -1
- package/dist/manifest-schema-kPGX7VS-.mjs.map +1 -0
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +5 -5
- package/dist/{media-BjKxjzk8.mjs → media-C5t_eCnk.mjs} +2 -2
- package/dist/{media-BjKxjzk8.mjs.map → media-C5t_eCnk.mjs.map} +1 -1
- package/dist/{menus-8O6xNcYW.mjs → menus-CBb6UrMU.mjs} +3 -3
- package/dist/{menus-8O6xNcYW.mjs.map → menus-CBb6UrMU.mjs.map} +1 -1
- package/dist/{menus-CxqJ-z-N.mjs → menus-CYH09a41.mjs} +50 -68
- package/dist/menus-CYH09a41.mjs.map +1 -0
- package/dist/{object-cache-SEb2IM4Z.mjs → object-cache-CHbHv83-.mjs} +8 -4
- package/dist/object-cache-CHbHv83-.mjs.map +1 -0
- package/dist/{options-BvClpP5Q.d.mts → options-41nCWqi9.d.mts} +2 -2
- package/dist/{options-BvClpP5Q.d.mts.map → options-41nCWqi9.d.mts.map} +1 -1
- package/dist/page/index.d.mts +5 -2
- package/dist/page/index.d.mts.map +1 -1
- package/dist/page/index.mjs +3 -2
- package/dist/page/index.mjs.map +1 -1
- package/dist/{parse-DqMWHQ8x.mjs → parse-BwAdS7FZ.mjs} +2 -2
- package/dist/{parse-DqMWHQ8x.mjs.map → parse-BwAdS7FZ.mjs.map} +1 -1
- package/dist/plugin-types.d.mts +4 -2
- package/dist/plugin-types.d.mts.map +1 -1
- package/dist/plugin-utils.d.mts +7 -7
- package/dist/plugins/adapt-sandbox-entry.d.mts +7 -7
- package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
- package/dist/{query-34pfm02Q.mjs → query-BpQb-blG.mjs} +14 -14
- package/dist/{query-34pfm02Q.mjs.map → query-BpQb-blG.mjs.map} +1 -1
- package/dist/{rate-limit-CBQK4ygJ.mjs → rate-limit-Dap6HMNM.mjs} +2 -2
- package/dist/{rate-limit-CBQK4ygJ.mjs.map → rate-limit-Dap6HMNM.mjs.map} +1 -1
- package/dist/{redirect-C1KVGYWe.mjs → redirect-_M0trWoo.mjs} +2 -2
- package/dist/{redirect-C1KVGYWe.mjs.map → redirect-_M0trWoo.mjs.map} +1 -1
- package/dist/{redirects-DgnypVir.mjs → redirects-w0V9vj_j.mjs} +3 -3
- package/dist/{redirects-DgnypVir.mjs.map → redirects-w0V9vj_j.mjs.map} +1 -1
- package/dist/{registry-3aOAS4My.mjs → registry-g8yYt4cC.mjs} +4 -4
- package/dist/{registry-3aOAS4My.mjs.map → registry-g8yYt4cC.mjs.map} +1 -1
- package/dist/{runner-CEtq-6Sm.d.mts → runner-BbR3DfrL.d.mts} +2 -2
- package/dist/{runner-CEtq-6Sm.d.mts.map → runner-BbR3DfrL.d.mts.map} +1 -1
- package/dist/{runner-BsquExlK.mjs → runner-DPdt_Wpa.mjs} +250 -178
- package/dist/runner-DPdt_Wpa.mjs.map +1 -0
- package/dist/runtime.d.mts +7 -7
- package/dist/runtime.mjs +2 -2
- package/dist/{schema-BGwx8AQY.mjs → schema-DoLd3j2e.mjs} +5 -5
- package/dist/{schema-BGwx8AQY.mjs.map → schema-DoLd3j2e.mjs.map} +1 -1
- package/dist/{search-Gl747zMP.mjs → search-BR0ZeZmh.mjs} +4 -4
- package/dist/{search-Gl747zMP.mjs.map → search-BR0ZeZmh.mjs.map} +1 -1
- package/dist/{sections-C-umuuLj.mjs → sections-5J2YJPqJ.mjs} +3 -3
- package/dist/{sections-C-umuuLj.mjs.map → sections-5J2YJPqJ.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +15 -15
- package/dist/{seo-BNzkiK_W.mjs → seo-DKeXHkzE.mjs} +3 -3
- package/dist/{seo-BNzkiK_W.mjs.map → seo-DKeXHkzE.mjs.map} +1 -1
- package/dist/{service-CQHK_10l.mjs → service-BbO2s8gT.mjs} +2 -2
- package/dist/{service-CQHK_10l.mjs.map → service-BbO2s8gT.mjs.map} +1 -1
- package/dist/{settings-fPSR6dJa.mjs → settings-BMNr9Dmi.mjs} +3 -3
- package/dist/{settings-fPSR6dJa.mjs.map → settings-BMNr9Dmi.mjs.map} +1 -1
- package/dist/{settings-C4tXRYiG.mjs → settings-DJeki-97.mjs} +4 -4
- package/dist/{settings-C4tXRYiG.mjs.map → settings-DJeki-97.mjs.map} +1 -1
- package/dist/{taxonomies-DbAtL6m4.mjs → taxonomies-CM_fUqLU.mjs} +5 -4
- package/dist/taxonomies-CM_fUqLU.mjs.map +1 -0
- package/dist/{taxonomies-n8w72lLQ.mjs → taxonomies-D13i2ctt.mjs} +75 -20
- package/dist/taxonomies-D13i2ctt.mjs.map +1 -0
- package/dist/{taxonomy-BBUlGug5.mjs → taxonomy-BU0LjdVb.mjs} +4 -4
- package/dist/{taxonomy-BBUlGug5.mjs.map → taxonomy-BU0LjdVb.mjs.map} +1 -1
- package/dist/{types-h2Cwbzi2.d.mts → types-BYnDBVwe.d.mts} +24 -5
- package/dist/{types-h2Cwbzi2.d.mts.map → types-BYnDBVwe.d.mts.map} +1 -1
- package/dist/{types-CmI1OyQg.mjs → types-CZgK3EMW.mjs} +2 -2
- package/dist/{types-CmI1OyQg.mjs.map → types-CZgK3EMW.mjs.map} +1 -1
- package/dist/{types-B-gIxFCE.d.mts → types-xIfVRNLp.d.mts} +38 -1
- package/dist/types-xIfVRNLp.d.mts.map +1 -0
- package/dist/{user-JAuc8VUf.mjs → user-DkG2jKCp.mjs} +3 -3
- package/dist/{user-JAuc8VUf.mjs.map → user-DkG2jKCp.mjs.map} +1 -1
- package/dist/{validate-Va_An3qb.d.mts → validate-CG0V0wNR.d.mts} +2 -2
- package/dist/{validate-Va_An3qb.d.mts.map → validate-CG0V0wNR.d.mts.map} +1 -1
- package/dist/{validate-BzXHWnkV.mjs → validate-CuJ1Ps9j.mjs} +2 -2
- package/dist/{validate-BzXHWnkV.mjs.map → validate-CuJ1Ps9j.mjs.map} +1 -1
- package/dist/{validation-B6DKPTtU.mjs → validation-CwUfTdGF.mjs} +5 -5
- package/dist/{validation-B6DKPTtU.mjs.map → validation-CwUfTdGF.mjs.map} +1 -1
- package/dist/version-BoNBItP_.mjs +7 -0
- package/dist/{version-DHdCA24u.mjs.map → version-BoNBItP_.mjs.map} +1 -1
- package/dist/{widgets-C2RbFN0G.mjs → widgets-BfOZ-ajY.mjs} +2 -2
- package/dist/{widgets-C2RbFN0G.mjs.map → widgets-BfOZ-ajY.mjs.map} +1 -1
- package/dist/{zod-generator-xEHODMJf.mjs → zod-generator-gFsRuNNy.mjs} +6 -3
- package/dist/zod-generator-gFsRuNNy.mjs.map +1 -0
- package/package.json +5 -5
- package/src/api/handlers/taxonomies.ts +5 -1
- package/src/astro/integration/virtual-modules.ts +15 -0
- package/src/astro/routes/sitemap-[collection].xml.ts +5 -4
- package/src/components/EmDashHead.astro +23 -3
- package/src/components/JsonLdScript.astro +9 -0
- package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +16 -0
- package/src/database/migrations/046_media_usage_index.ts +115 -0
- package/src/database/migrations/047_restore_taxonomy_parent_index.ts +29 -0
- package/src/database/migrations/runner.ts +4 -0
- package/src/database/repositories/media-usage.ts +444 -0
- package/src/database/types.ts +39 -0
- package/src/emdash-runtime.ts +61 -52
- package/src/index.ts +3 -0
- package/src/loader.ts +110 -31
- package/src/media/usage/extractor.ts +302 -0
- package/src/media/usage/types.ts +46 -0
- package/src/object-cache/index.ts +8 -2
- package/src/page/metadata.ts +23 -3
- package/src/plugin-types.ts +10 -0
- package/src/plugins/define-plugin.ts +14 -5
- package/src/plugins/hooks.ts +53 -65
- package/src/plugins/index.ts +5 -0
- package/src/plugins/manager.ts +22 -0
- package/src/plugins/manifest-schema.ts +2 -0
- package/src/plugins/types.ts +35 -3
- package/src/schema/zod-generator.ts +9 -1
- package/src/seed/apply.ts +6 -0
- package/src/taxonomies/index.ts +121 -18
- package/dist/apply-Bk2mAuLn.mjs.map +0 -1
- package/dist/index-Cz6_JBL3.d.mts.map +0 -1
- package/dist/loader-DrpofRon.mjs.map +0 -1
- package/dist/manifest-schema-BOfPXse0.mjs.map +0 -1
- package/dist/menus-CxqJ-z-N.mjs.map +0 -1
- package/dist/object-cache-SEb2IM4Z.mjs.map +0 -1
- package/dist/runner-BsquExlK.mjs.map +0 -1
- package/dist/taxonomies-DbAtL6m4.mjs.map +0 -1
- package/dist/taxonomies-n8w72lLQ.mjs.map +0 -1
- package/dist/types-B-gIxFCE.d.mts.map +0 -1
- package/dist/version-DHdCA24u.mjs +0 -7
- package/dist/zod-generator-xEHODMJf.mjs.map +0 -1
package/src/emdash-runtime.ts
CHANGED
|
@@ -2857,11 +2857,25 @@ export class EmDashRuntime {
|
|
|
2857
2857
|
}
|
|
2858
2858
|
|
|
2859
2859
|
async handleContentSchedule(collection: string, id: string, scheduledAt: string) {
|
|
2860
|
-
|
|
2860
|
+
const result = await handleContentSchedule(this.db, collection, id, scheduledAt);
|
|
2861
|
+
|
|
2862
|
+
// Run afterSchedule hooks (fire-and-forget)
|
|
2863
|
+
if (result.success && result.data) {
|
|
2864
|
+
this.runAfterScheduleHooks(contentItemToRecord(result.data.item), collection);
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
return result;
|
|
2861
2868
|
}
|
|
2862
2869
|
|
|
2863
2870
|
async handleContentUnschedule(collection: string, id: string) {
|
|
2864
|
-
|
|
2871
|
+
const result = await handleContentUnschedule(this.db, collection, id);
|
|
2872
|
+
|
|
2873
|
+
// Run afterUnschedule hooks (fire-and-forget)
|
|
2874
|
+
if (result.success && result.data) {
|
|
2875
|
+
this.runAfterUnscheduleHooks(contentItemToRecord(result.data.item), collection);
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
return result;
|
|
2865
2879
|
}
|
|
2866
2880
|
|
|
2867
2881
|
async handleContentCountScheduled(collection: string) {
|
|
@@ -3320,14 +3334,41 @@ export class EmDashRuntime {
|
|
|
3320
3334
|
}
|
|
3321
3335
|
}
|
|
3322
3336
|
|
|
3323
|
-
private
|
|
3337
|
+
private runDeferredContentHook(
|
|
3338
|
+
name:
|
|
3339
|
+
| "content:afterPublish"
|
|
3340
|
+
| "content:afterUnpublish"
|
|
3341
|
+
| "content:afterRestore"
|
|
3342
|
+
| "content:afterSchedule"
|
|
3343
|
+
| "content:afterUnschedule",
|
|
3344
|
+
content: Record<string, unknown>,
|
|
3345
|
+
collection: string,
|
|
3346
|
+
): void {
|
|
3347
|
+
const label = name.slice("content:".length);
|
|
3348
|
+
|
|
3324
3349
|
after(async () => {
|
|
3325
3350
|
// Trusted plugins
|
|
3326
|
-
if (this.hooks.hasHooks(
|
|
3351
|
+
if (this.hooks.hasHooks(name)) {
|
|
3327
3352
|
try {
|
|
3328
|
-
|
|
3353
|
+
switch (name) {
|
|
3354
|
+
case "content:afterPublish":
|
|
3355
|
+
await this.hooks.runContentAfterPublish(content, collection);
|
|
3356
|
+
break;
|
|
3357
|
+
case "content:afterUnpublish":
|
|
3358
|
+
await this.hooks.runContentAfterUnpublish(content, collection);
|
|
3359
|
+
break;
|
|
3360
|
+
case "content:afterRestore":
|
|
3361
|
+
await this.hooks.runContentAfterRestore(content, collection);
|
|
3362
|
+
break;
|
|
3363
|
+
case "content:afterSchedule":
|
|
3364
|
+
await this.hooks.runContentAfterSchedule(content, collection);
|
|
3365
|
+
break;
|
|
3366
|
+
case "content:afterUnschedule":
|
|
3367
|
+
await this.hooks.runContentAfterUnschedule(content, collection);
|
|
3368
|
+
break;
|
|
3369
|
+
}
|
|
3329
3370
|
} catch (err) {
|
|
3330
|
-
console.error(
|
|
3371
|
+
console.error(`EmDash ${label} hook error:`, err);
|
|
3331
3372
|
}
|
|
3332
3373
|
}
|
|
3333
3374
|
|
|
@@ -3340,9 +3381,9 @@ export class EmDashRuntime {
|
|
|
3340
3381
|
tasks.push(
|
|
3341
3382
|
(async () => {
|
|
3342
3383
|
try {
|
|
3343
|
-
await plugin.invokeHook(
|
|
3384
|
+
await plugin.invokeHook(name, { content, collection });
|
|
3344
3385
|
} catch (err) {
|
|
3345
|
-
console.error(`EmDash: Sandboxed plugin ${pluginId}
|
|
3386
|
+
console.error(`EmDash: Sandboxed plugin ${pluginId} ${label} error:`, err);
|
|
3346
3387
|
}
|
|
3347
3388
|
})(),
|
|
3348
3389
|
);
|
|
@@ -3351,56 +3392,24 @@ export class EmDashRuntime {
|
|
|
3351
3392
|
});
|
|
3352
3393
|
}
|
|
3353
3394
|
|
|
3354
|
-
private
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
this.hooks
|
|
3358
|
-
.runContentAfterUnpublish(content, collection)
|
|
3359
|
-
.catch((err) => console.error("EmDash afterUnpublish hook error:", err));
|
|
3360
|
-
}
|
|
3361
|
-
|
|
3362
|
-
// Sandboxed plugins
|
|
3363
|
-
for (const [pluginKey, plugin] of this.sandboxedPlugins) {
|
|
3364
|
-
const [pluginId] = pluginKey.split(":");
|
|
3365
|
-
if (!pluginId || !this.isPluginEnabled(pluginId)) continue;
|
|
3395
|
+
private runAfterPublishHooks(content: Record<string, unknown>, collection: string): void {
|
|
3396
|
+
this.runDeferredContentHook("content:afterPublish", content, collection);
|
|
3397
|
+
}
|
|
3366
3398
|
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
.catch((err) =>
|
|
3370
|
-
console.error(`EmDash: Sandboxed plugin ${pluginId} afterUnpublish error:`, err),
|
|
3371
|
-
);
|
|
3372
|
-
}
|
|
3399
|
+
private runAfterUnpublishHooks(content: Record<string, unknown>, collection: string): void {
|
|
3400
|
+
this.runDeferredContentHook("content:afterUnpublish", content, collection);
|
|
3373
3401
|
}
|
|
3374
3402
|
|
|
3375
3403
|
private runAfterRestoreHooks(content: Record<string, unknown>, collection: string): void {
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
if (this.hooks.hasHooks("content:afterRestore")) {
|
|
3379
|
-
try {
|
|
3380
|
-
await this.hooks.runContentAfterRestore(content, collection);
|
|
3381
|
-
} catch (err) {
|
|
3382
|
-
console.error("EmDash afterRestore hook error:", err);
|
|
3383
|
-
}
|
|
3384
|
-
}
|
|
3404
|
+
this.runDeferredContentHook("content:afterRestore", content, collection);
|
|
3405
|
+
}
|
|
3385
3406
|
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
const [pluginId] = pluginKey.split(":");
|
|
3390
|
-
if (!pluginId || !this.isPluginEnabled(pluginId)) continue;
|
|
3407
|
+
private runAfterScheduleHooks(content: Record<string, unknown>, collection: string): void {
|
|
3408
|
+
this.runDeferredContentHook("content:afterSchedule", content, collection);
|
|
3409
|
+
}
|
|
3391
3410
|
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
try {
|
|
3395
|
-
await plugin.invokeHook("content:afterRestore", { content, collection });
|
|
3396
|
-
} catch (err) {
|
|
3397
|
-
console.error(`EmDash: Sandboxed plugin ${pluginId} afterRestore error:`, err);
|
|
3398
|
-
}
|
|
3399
|
-
})(),
|
|
3400
|
-
);
|
|
3401
|
-
}
|
|
3402
|
-
await Promise.allSettled(tasks);
|
|
3403
|
-
});
|
|
3411
|
+
private runAfterUnscheduleHooks(content: Record<string, unknown>, collection: string): void {
|
|
3412
|
+
this.runDeferredContentHook("content:afterUnschedule", content, collection);
|
|
3404
3413
|
}
|
|
3405
3414
|
|
|
3406
3415
|
private async handleSandboxedRoute(
|
package/src/index.ts
CHANGED
|
@@ -255,6 +255,9 @@ export type {
|
|
|
255
255
|
ContentHookEvent,
|
|
256
256
|
ContentDeleteEvent,
|
|
257
257
|
ContentPublishStateChangeEvent,
|
|
258
|
+
ContentRestoreStateChangeEvent,
|
|
259
|
+
ContentScheduleStateChangeEvent,
|
|
260
|
+
ContentStateChangeEvent,
|
|
258
261
|
MediaUploadEvent,
|
|
259
262
|
HookResult,
|
|
260
263
|
PluginRoute,
|
package/src/loader.ts
CHANGED
|
@@ -25,15 +25,23 @@ import { isMissingColumnError, isMissingTableError } from "./utils/db-errors.js"
|
|
|
25
25
|
const FIELD_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* SEO columns
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* SEO columns folded into the single-entry query as a single JSON column
|
|
29
|
+
* (`_emdash_seo` in the result set), then expanded onto the row under these
|
|
30
|
+
* aliases for `extractSeo()`. Surfacing SEO as one aggregated column keeps the
|
|
31
|
+
* result-set width bounded regardless of how many fields the collection has,
|
|
32
|
+
* which matters for D1: a flat `LEFT JOIN _emdash_seo` adds 5 alias columns to
|
|
33
|
+
* every row and pushes wide collections (common after WordPress / ACF imports)
|
|
34
|
+
* past D1's per-result-set column limit, surfacing as a silent null entry.
|
|
35
|
+
* One JSON column is one column, so the join stays safe at any schema width.
|
|
36
|
+
*
|
|
37
|
+
* The aliases mirror the strategy used by `foldedHydrationSelects` for byline
|
|
38
|
+
* and taxonomy hydration: aggregate in SQL, expand in JS. SEO is 1:1 with
|
|
39
|
+
* content, so the subquery uses `json_object` (not the array aggregator).
|
|
32
40
|
*
|
|
33
41
|
* The `_emdash_` prefix on the aliases guarantees they can never collide with
|
|
34
42
|
* a content field. Field slugs must match `/^[a-z][a-z0-9_]*$/`, so a user can
|
|
35
|
-
* legitimately define a `seo_title` field;
|
|
36
|
-
*
|
|
43
|
+
* legitimately define a `seo_title` field; surfacing the SEO column under its
|
|
44
|
+
* bare name would shadow that field in the result set and drop the user's
|
|
37
45
|
* value. The prefix (illegal as a leading slug char) sidesteps this entirely.
|
|
38
46
|
*/
|
|
39
47
|
const SEO_COLUMN_ALIASES: Record<string, string> = {
|
|
@@ -47,6 +55,9 @@ const SEO_COLUMN_ALIASES: Record<string, string> = {
|
|
|
47
55
|
/** Aliased SEO result keys — excluded from generic field mapping. */
|
|
48
56
|
const SEO_ALIAS_COLUMNS = Object.values(SEO_COLUMN_ALIASES);
|
|
49
57
|
|
|
58
|
+
/** Folded SEO JSON column name in the result set (expanded onto aliases in JS). */
|
|
59
|
+
const SEO_FOLDED_COLUMN = "_emdash_seo";
|
|
60
|
+
|
|
50
61
|
/**
|
|
51
62
|
* System columns excluded from entry.data
|
|
52
63
|
* Note: slug is intentionally NOT excluded - it's useful as data.slug in templates
|
|
@@ -67,15 +78,17 @@ const SYSTEM_COLUMNS = new Set([
|
|
|
67
78
|
"draft_revision_id",
|
|
68
79
|
"locale",
|
|
69
80
|
"translation_group",
|
|
70
|
-
// Aliased SEO columns
|
|
71
|
-
// Surfaced as a nested data.seo object (see
|
|
72
|
-
// fields. The aliases are _emdash_-prefixed so
|
|
73
|
-
// field named e.g. `seo_title`.
|
|
81
|
+
// Aliased SEO columns expanded from the folded _emdash_seo JSON column on
|
|
82
|
+
// the single-entry path. Surfaced as a nested data.seo object (see
|
|
83
|
+
// extractSeo), never as flat fields. The aliases are _emdash_-prefixed so
|
|
84
|
+
// they can't shadow a user field named e.g. `seo_title`.
|
|
74
85
|
...SEO_ALIAS_COLUMNS,
|
|
75
|
-
// Folded hydration JSON columns (see foldedHydrationSelects
|
|
76
|
-
// the FOLDED_* markers
|
|
86
|
+
// Folded hydration JSON columns (see foldedHydrationSelects and
|
|
87
|
+
// foldedSeoSelect) — surfaced via the FOLDED_* markers or expanded onto
|
|
88
|
+
// SEO_ALIAS_COLUMNS, never as flat fields.
|
|
77
89
|
"_emdash_terms",
|
|
78
90
|
"_emdash_bylines",
|
|
91
|
+
SEO_FOLDED_COLUMN,
|
|
79
92
|
]);
|
|
80
93
|
|
|
81
94
|
/** Markers for byline/taxonomy hydration folded into the content query. */
|
|
@@ -123,6 +136,62 @@ function foldedHydrationSelects(db: Kysely<any>, type: string, outer: string) {
|
|
|
123
136
|
return { terms, bylines };
|
|
124
137
|
}
|
|
125
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Correlated JSON-object subquery that folds per-entry SEO into the content
|
|
141
|
+
* query without widening the result set: 1 row of `_emdash_seo` becomes 1 JSON
|
|
142
|
+
* column rather than 5 flat columns. The JSON column is expanded onto the row
|
|
143
|
+
* via {@link expandFoldedSeo} after the query runs, preserving the alias keys
|
|
144
|
+
* that {@link extractSeo} reads. Missing SEO row (no entry in `_emdash_seo`)
|
|
145
|
+
* yields NULL, which {@link expandFoldedSeo} treats as "no SEO" - identical to
|
|
146
|
+
* the prior LEFT JOIN miss behavior.
|
|
147
|
+
*
|
|
148
|
+
* Dialect-specific aggregation mirrors {@link foldedHydrationSelects}: SQLite
|
|
149
|
+
* `json_object` returns a JSON *string*, Postgres `json_build_object` returns
|
|
150
|
+
* parsed JSON; both branches are handled in expansion.
|
|
151
|
+
*/
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- any Kysely instance
|
|
153
|
+
function foldedSeoSelect(db: Kysely<any>, type: string, outer: string) {
|
|
154
|
+
const o = sql.ref(outer);
|
|
155
|
+
const pg = isPostgres(db);
|
|
156
|
+
// Use raw column names (not aliases) as JSON keys: the JSON is expanded back
|
|
157
|
+
// onto SEO_COLUMN_ALIASES in JS, and keeping the keys matched to the
|
|
158
|
+
// underlying columns makes the SQL readable and the expansion 1-to-1.
|
|
159
|
+
const pairs =
|
|
160
|
+
"'seo_title', s.seo_title, 'seo_description', s.seo_description, 'seo_image', s.seo_image, 'seo_canonical', s.seo_canonical, 'seo_no_index', s.seo_no_index";
|
|
161
|
+
const obj = pg ? sql.raw(`json_build_object(${pairs})`) : sql.raw(`json_object(${pairs})`);
|
|
162
|
+
return sql`(SELECT ${obj} FROM ${sql.ref("_emdash_seo")} AS s WHERE s.collection = ${type} AND s.content_id = ${o}.id LIMIT 1) AS ${sql.ref(SEO_FOLDED_COLUMN)}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Expand the folded `_emdash_seo` JSON column onto the row using SEO_COLUMN_ALIASES,
|
|
167
|
+
* so {@link extractSeo} reads it transparently. SQLite returns a JSON string
|
|
168
|
+
* (parse it); Postgres returns already-parsed JSON. Missing/malformed/null is
|
|
169
|
+
* a no-op: {@link extractSeo} returns null when the aliases are absent.
|
|
170
|
+
*/
|
|
171
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
172
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function expandFoldedSeo(row: Record<string, unknown>): void {
|
|
176
|
+
const raw = row[SEO_FOLDED_COLUMN];
|
|
177
|
+
delete row[SEO_FOLDED_COLUMN];
|
|
178
|
+
let parsed: Record<string, unknown> | null = null;
|
|
179
|
+
if (typeof raw === "string") {
|
|
180
|
+
try {
|
|
181
|
+
const candidate: unknown = JSON.parse(raw);
|
|
182
|
+
if (isPlainObject(candidate)) parsed = candidate;
|
|
183
|
+
} catch {
|
|
184
|
+
return; // malformed JSON: leave the row without SEO aliases (extractSeo returns null)
|
|
185
|
+
}
|
|
186
|
+
} else if (isPlainObject(raw)) {
|
|
187
|
+
parsed = raw;
|
|
188
|
+
}
|
|
189
|
+
if (!parsed) return;
|
|
190
|
+
for (const [col, alias] of Object.entries(SEO_COLUMN_ALIASES)) {
|
|
191
|
+
row[alias] = parsed[col] ?? null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
126
195
|
/**
|
|
127
196
|
* Stash folded hydration JSON (non-enumerable) for the query.ts fast paths.
|
|
128
197
|
* SQLite returns a JSON string (parse it); Postgres returns already-parsed JSON.
|
|
@@ -227,6 +296,18 @@ async function getTaxonomyNames(db: Kysely<Database>): Promise<Set<string>> {
|
|
|
227
296
|
}
|
|
228
297
|
}
|
|
229
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Reset the module-scoped taxonomy-names cache.
|
|
301
|
+
*
|
|
302
|
+
* Called from `invalidateTaxonomyDefsCache()` so that creating or seeding a
|
|
303
|
+
* taxonomy definition is reflected within the current isolate instead of
|
|
304
|
+
* waiting for the isolate to recycle. Keeps this cache consistent with the
|
|
305
|
+
* isolate-wide taxonomy-defs cache in `taxonomies/index.ts`.
|
|
306
|
+
*/
|
|
307
|
+
export function resetTaxonomyNamesCache(): void {
|
|
308
|
+
taxonomyNames = null;
|
|
309
|
+
}
|
|
310
|
+
|
|
230
311
|
/**
|
|
231
312
|
* System columns to include in data (mapped to camelCase where needed)
|
|
232
313
|
*/
|
|
@@ -879,11 +960,12 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
879
960
|
taxonomyFilters.map(
|
|
880
961
|
(f) => sql`AND EXISTS (
|
|
881
962
|
SELECT 1 FROM content_taxonomies ct
|
|
882
|
-
INNER JOIN taxonomies t ON t.
|
|
963
|
+
INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id
|
|
883
964
|
WHERE ct.collection = ${type}
|
|
884
965
|
AND ct.entry_id = ${sql.ref(tableName)}.id
|
|
885
966
|
AND t.name = ${f.name}
|
|
886
967
|
AND t.slug IN (${sql.join(f.slugs.map((s) => sql`${s}`))})
|
|
968
|
+
${locale ? sql`AND t.locale = ${locale}` : sql``}
|
|
887
969
|
)`,
|
|
888
970
|
),
|
|
889
971
|
sql` `,
|
|
@@ -1050,31 +1132,25 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
1050
1132
|
// When locale is specified, prefer locale-scoped slug match,
|
|
1051
1133
|
// but IDs are globally unique so always check id without locale scope.
|
|
1052
1134
|
//
|
|
1053
|
-
//
|
|
1054
|
-
//
|
|
1055
|
-
//
|
|
1056
|
-
//
|
|
1057
|
-
//
|
|
1058
|
-
//
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
),
|
|
1063
|
-
);
|
|
1064
|
-
// Fold byline + taxonomy hydration into the content query (see
|
|
1065
|
-
// foldedHydrationSelects), removing the two separate hydration
|
|
1066
|
-
// round trips per fetch.
|
|
1135
|
+
// Byline + taxonomy hydration (foldedHydrationSelects) and per-entry
|
|
1136
|
+
// SEO (foldedSeoSelect) are each surfaced as a single aggregated JSON
|
|
1137
|
+
// column rather than flat columns. This keeps the result-set width
|
|
1138
|
+
// bounded at any collection schema width: a flat `LEFT JOIN _emdash_seo`
|
|
1139
|
+
// adds 5 alias columns to every row and pushes wide flat-schema
|
|
1140
|
+
// collections (common after WordPress / ACF imports) past D1's
|
|
1141
|
+
// per-result-set column limit, surfacing as a silent null entry. One
|
|
1142
|
+
// JSON column is one column, so the join stays safe at any width and
|
|
1143
|
+
// we keep the single round trip.
|
|
1067
1144
|
const { terms: termsSelect, bylines: bylinesSelect } = foldedHydrationSelects(
|
|
1068
1145
|
db,
|
|
1069
1146
|
type,
|
|
1070
1147
|
"c",
|
|
1071
1148
|
);
|
|
1149
|
+
const seoSelect = foldedSeoSelect(db, type, "c");
|
|
1072
1150
|
const result = locale
|
|
1073
1151
|
? await sql<Record<string, unknown>>`
|
|
1074
1152
|
SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
|
|
1075
1153
|
FROM ${sql.ref(tableName)} AS c
|
|
1076
|
-
LEFT JOIN ${sql.ref("_emdash_seo")} AS s
|
|
1077
|
-
ON s.collection = ${type} AND s.content_id = c.id
|
|
1078
1154
|
WHERE c.deleted_at IS NULL
|
|
1079
1155
|
AND ((c.slug = ${id} AND c.locale = ${locale}) OR c.id = ${id})
|
|
1080
1156
|
LIMIT 1
|
|
@@ -1082,8 +1158,6 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
1082
1158
|
: await sql<Record<string, unknown>>`
|
|
1083
1159
|
SELECT c.*, ${seoSelect}, ${termsSelect}, ${bylinesSelect}
|
|
1084
1160
|
FROM ${sql.ref(tableName)} AS c
|
|
1085
|
-
LEFT JOIN ${sql.ref("_emdash_seo")} AS s
|
|
1086
|
-
ON s.collection = ${type} AND s.content_id = c.id
|
|
1087
1161
|
WHERE c.deleted_at IS NULL
|
|
1088
1162
|
AND (c.slug = ${id} OR c.id = ${id})
|
|
1089
1163
|
LIMIT 1
|
|
@@ -1094,6 +1168,11 @@ export function emdashLoader(): LiveLoader<EntryData, EntryFilter, CollectionFil
|
|
|
1094
1168
|
return undefined;
|
|
1095
1169
|
}
|
|
1096
1170
|
|
|
1171
|
+
// Expand the folded SEO JSON column onto SEO_COLUMN_ALIASES so
|
|
1172
|
+
// extractSeo() reads it transparently. Missing/null SEO is a
|
|
1173
|
+
// no-op: extractSeo() returns null when the aliases are absent.
|
|
1174
|
+
expandFoldedSeo(row);
|
|
1175
|
+
|
|
1097
1176
|
const i18nConfig = virtualConfig?.i18n;
|
|
1098
1177
|
const i18nEnabled = i18nConfig && i18nConfig.locales.length > 1;
|
|
1099
1178
|
const entrySlug = rowStr(row, "slug") || rowStr(row, "id");
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { normalizeMime } from "../mime.js";
|
|
2
|
+
import type {
|
|
3
|
+
ExtractedMediaUsageOccurrence,
|
|
4
|
+
ExtractMediaUsageOccurrencesInput,
|
|
5
|
+
MediaKind,
|
|
6
|
+
MediaUsageExtractionSubField,
|
|
7
|
+
MediaUsageReferenceType,
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
|
|
10
|
+
const INTERNAL_MEDIA_PREFIX = "/_emdash/api/media/file/";
|
|
11
|
+
const URL_LIKE_RE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
12
|
+
|
|
13
|
+
interface MediaRef {
|
|
14
|
+
mediaId: string | null;
|
|
15
|
+
provider: string;
|
|
16
|
+
providerAssetId: string;
|
|
17
|
+
mediaKind: MediaKind | null;
|
|
18
|
+
mimeType: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface AddOccurrenceInput {
|
|
22
|
+
fieldSlug: string;
|
|
23
|
+
fieldPath: string;
|
|
24
|
+
referenceType: MediaUsageReferenceType;
|
|
25
|
+
value: unknown;
|
|
26
|
+
fallbackKind: MediaKind | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function extractMediaUsageOccurrences({
|
|
30
|
+
fields,
|
|
31
|
+
data,
|
|
32
|
+
}: ExtractMediaUsageOccurrencesInput): ExtractedMediaUsageOccurrence[] {
|
|
33
|
+
const occurrences: ExtractedMediaUsageOccurrence[] = [];
|
|
34
|
+
const seen = new Set<string>();
|
|
35
|
+
|
|
36
|
+
for (const field of fields) {
|
|
37
|
+
const value = data[field.slug];
|
|
38
|
+
|
|
39
|
+
if (field.type === "image") {
|
|
40
|
+
addOccurrence(occurrences, seen, {
|
|
41
|
+
fieldSlug: field.slug,
|
|
42
|
+
fieldPath: field.slug,
|
|
43
|
+
referenceType: "image_field",
|
|
44
|
+
value,
|
|
45
|
+
fallbackKind: "image",
|
|
46
|
+
});
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (field.type === "file") {
|
|
51
|
+
addOccurrence(occurrences, seen, {
|
|
52
|
+
fieldSlug: field.slug,
|
|
53
|
+
fieldPath: field.slug,
|
|
54
|
+
referenceType: "file_field",
|
|
55
|
+
value,
|
|
56
|
+
fallbackKind: null,
|
|
57
|
+
});
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (field.type === "repeater") {
|
|
62
|
+
extractRepeaterOccurrences(occurrences, seen, field.slug, value, field.validation?.subFields);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (field.type === "portableText") {
|
|
67
|
+
extractPortableTextOccurrences(occurrences, seen, field.slug, value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return occurrences;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function extractRepeaterOccurrences(
|
|
75
|
+
occurrences: ExtractedMediaUsageOccurrence[],
|
|
76
|
+
seen: Set<string>,
|
|
77
|
+
fieldSlug: string,
|
|
78
|
+
value: unknown,
|
|
79
|
+
subFields: readonly MediaUsageExtractionSubField[] | undefined,
|
|
80
|
+
): void {
|
|
81
|
+
if (!Array.isArray(value) || !Array.isArray(subFields)) return;
|
|
82
|
+
|
|
83
|
+
for (const [itemIndex, item] of value.entries()) {
|
|
84
|
+
if (!isRecord(item)) continue;
|
|
85
|
+
|
|
86
|
+
for (const subField of subFields) {
|
|
87
|
+
if (subField.type !== "image" && subField.type !== "file") continue;
|
|
88
|
+
|
|
89
|
+
addOccurrence(occurrences, seen, {
|
|
90
|
+
fieldSlug,
|
|
91
|
+
fieldPath: `${fieldSlug}[${itemIndex}].${subField.slug}`,
|
|
92
|
+
referenceType: subField.type === "image" ? "image_field" : "file_field",
|
|
93
|
+
value: item[subField.slug],
|
|
94
|
+
fallbackKind: subField.type === "image" ? "image" : null,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function extractPortableTextOccurrences(
|
|
101
|
+
occurrences: ExtractedMediaUsageOccurrence[],
|
|
102
|
+
seen: Set<string>,
|
|
103
|
+
fieldSlug: string,
|
|
104
|
+
value: unknown,
|
|
105
|
+
): void {
|
|
106
|
+
if (!Array.isArray(value)) return;
|
|
107
|
+
|
|
108
|
+
for (const [blockIndex, block] of value.entries()) {
|
|
109
|
+
if (!isRecord(block) || block._type !== "image" || !isRecord(block.asset)) continue;
|
|
110
|
+
|
|
111
|
+
const provider = normalizeProvider(block.asset.provider);
|
|
112
|
+
const ref = readPortableTextAssetRef(block.asset, provider);
|
|
113
|
+
if (!ref) continue;
|
|
114
|
+
|
|
115
|
+
addRefOccurrence(occurrences, seen, {
|
|
116
|
+
fieldSlug,
|
|
117
|
+
fieldPath: `${fieldSlug}[${blockIndex}].asset.${ref.key}`,
|
|
118
|
+
referenceType: "portable_text_image",
|
|
119
|
+
ref: buildMediaRef({
|
|
120
|
+
id: ref.id,
|
|
121
|
+
provider,
|
|
122
|
+
mimeType: normalizeMimeValue(block.asset.mimeType),
|
|
123
|
+
fallbackKind: "image",
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function addOccurrence(
|
|
130
|
+
occurrences: ExtractedMediaUsageOccurrence[],
|
|
131
|
+
seen: Set<string>,
|
|
132
|
+
input: AddOccurrenceInput,
|
|
133
|
+
): void {
|
|
134
|
+
const ref = readMediaRef(input.value, input.fallbackKind);
|
|
135
|
+
if (!ref) return;
|
|
136
|
+
|
|
137
|
+
addRefOccurrence(occurrences, seen, {
|
|
138
|
+
fieldSlug: input.fieldSlug,
|
|
139
|
+
fieldPath: input.fieldPath,
|
|
140
|
+
referenceType: input.referenceType,
|
|
141
|
+
ref,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function addRefOccurrence(
|
|
146
|
+
occurrences: ExtractedMediaUsageOccurrence[],
|
|
147
|
+
seen: Set<string>,
|
|
148
|
+
input: {
|
|
149
|
+
fieldSlug: string;
|
|
150
|
+
fieldPath: string;
|
|
151
|
+
referenceType: MediaUsageReferenceType;
|
|
152
|
+
ref: MediaRef | null;
|
|
153
|
+
},
|
|
154
|
+
): void {
|
|
155
|
+
if (!input.ref) return;
|
|
156
|
+
|
|
157
|
+
const occurrence: ExtractedMediaUsageOccurrence = {
|
|
158
|
+
fieldSlug: input.fieldSlug,
|
|
159
|
+
fieldPath: input.fieldPath,
|
|
160
|
+
occurrenceIndex: 0,
|
|
161
|
+
referenceType: input.referenceType,
|
|
162
|
+
mediaId: input.ref.mediaId,
|
|
163
|
+
provider: input.ref.provider,
|
|
164
|
+
providerAssetId: input.ref.providerAssetId,
|
|
165
|
+
mediaKind: input.ref.mediaKind,
|
|
166
|
+
mimeType: input.ref.mimeType,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const key = [
|
|
170
|
+
occurrence.fieldSlug,
|
|
171
|
+
occurrence.fieldPath,
|
|
172
|
+
occurrence.occurrenceIndex,
|
|
173
|
+
occurrence.referenceType,
|
|
174
|
+
occurrence.provider,
|
|
175
|
+
occurrence.providerAssetId,
|
|
176
|
+
occurrence.mediaId ?? "",
|
|
177
|
+
].join("\0");
|
|
178
|
+
|
|
179
|
+
if (seen.has(key)) return;
|
|
180
|
+
seen.add(key);
|
|
181
|
+
occurrences.push(occurrence);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function readMediaRef(value: unknown, fallbackKind: MediaKind | null): MediaRef | null {
|
|
185
|
+
if (typeof value === "string") {
|
|
186
|
+
const id = normalizeLocalMediaId(value);
|
|
187
|
+
return id ? buildMediaRef({ id, provider: "local", mimeType: null, fallbackKind }) : null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!isRecord(value)) return null;
|
|
191
|
+
|
|
192
|
+
const provider = normalizeProvider(value.provider);
|
|
193
|
+
const id = provider === "local" ? normalizeLocalMediaId(value.id) : normalizeStableId(value.id);
|
|
194
|
+
if (!id) return null;
|
|
195
|
+
|
|
196
|
+
return buildMediaRef({
|
|
197
|
+
id,
|
|
198
|
+
provider,
|
|
199
|
+
mimeType: normalizeMimeValue(value.mimeType),
|
|
200
|
+
fallbackKind,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function buildMediaRef(input: {
|
|
205
|
+
id: string;
|
|
206
|
+
provider: string;
|
|
207
|
+
mimeType: string | null;
|
|
208
|
+
fallbackKind: MediaKind | null;
|
|
209
|
+
}): MediaRef | null {
|
|
210
|
+
const provider = normalizeProvider(input.provider);
|
|
211
|
+
if (provider === "external") return null;
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
mediaId: provider === "local" ? input.id : null,
|
|
215
|
+
provider,
|
|
216
|
+
providerAssetId: input.id,
|
|
217
|
+
mediaKind: mediaKindFromMime(input.mimeType) ?? input.fallbackKind,
|
|
218
|
+
mimeType: input.mimeType,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function readPortableTextAssetRef(
|
|
223
|
+
asset: Record<string, unknown>,
|
|
224
|
+
provider: string,
|
|
225
|
+
): { key: "_ref" | "id"; id: string } | null {
|
|
226
|
+
const normalizeId = provider === "local" ? normalizeLocalMediaId : normalizeStableId;
|
|
227
|
+
const ref = normalizeId(asset._ref);
|
|
228
|
+
if (ref) return { key: "_ref", id: ref };
|
|
229
|
+
|
|
230
|
+
const id = normalizeId(asset.id);
|
|
231
|
+
if (id) return { key: "id", id };
|
|
232
|
+
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function normalizeProvider(value: unknown): string {
|
|
237
|
+
const provider = readString(value)?.trim();
|
|
238
|
+
return provider || "local";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function normalizeLocalMediaId(value: unknown): string | null {
|
|
242
|
+
const id = normalizeStableId(value);
|
|
243
|
+
if (!id) return null;
|
|
244
|
+
return id.includes("/") ? null : id;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function normalizeStableId(value: unknown): string | null {
|
|
248
|
+
if (typeof value !== "string") return null;
|
|
249
|
+
const trimmed = value.trim();
|
|
250
|
+
if (!trimmed) return null;
|
|
251
|
+
if (URL_LIKE_RE.test(trimmed)) return null;
|
|
252
|
+
if (trimmed.startsWith(INTERNAL_MEDIA_PREFIX)) return null;
|
|
253
|
+
return trimmed;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function normalizeMimeValue(value: unknown): string | null {
|
|
257
|
+
if (typeof value !== "string") return null;
|
|
258
|
+
const normalized = normalizeMime(value);
|
|
259
|
+
return normalized.includes("/") ? normalized : null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function mediaKindFromMime(mimeType: string | null): MediaKind | null {
|
|
263
|
+
if (!mimeType) return null;
|
|
264
|
+
if (mimeType.startsWith("image/")) return "image";
|
|
265
|
+
if (mimeType.startsWith("video/")) return "video";
|
|
266
|
+
if (mimeType.startsWith("audio/")) return "audio";
|
|
267
|
+
if (mimeType.startsWith("font/") || mimeType.startsWith("application/font-")) return "font";
|
|
268
|
+
if (mimeType.startsWith("text/")) return "text";
|
|
269
|
+
if (isDocumentMime(mimeType)) return "document";
|
|
270
|
+
if (isArchiveMime(mimeType)) return "archive";
|
|
271
|
+
return "other";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isDocumentMime(mimeType: string): boolean {
|
|
275
|
+
return (
|
|
276
|
+
mimeType === "application/pdf" ||
|
|
277
|
+
mimeType === "application/msword" ||
|
|
278
|
+
mimeType === "application/rtf" ||
|
|
279
|
+
mimeType === "application/vnd.ms-excel" ||
|
|
280
|
+
mimeType === "application/vnd.ms-powerpoint" ||
|
|
281
|
+
mimeType.startsWith("application/vnd.openxmlformats-officedocument.")
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function isArchiveMime(mimeType: string): boolean {
|
|
286
|
+
return (
|
|
287
|
+
mimeType === "application/zip" ||
|
|
288
|
+
mimeType === "application/gzip" ||
|
|
289
|
+
mimeType === "application/x-tar" ||
|
|
290
|
+
mimeType === "application/x-7z-compressed" ||
|
|
291
|
+
mimeType === "application/x-rar-compressed" ||
|
|
292
|
+
mimeType === "application/vnd.rar"
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function readString(value: unknown): string | null {
|
|
297
|
+
return typeof value === "string" ? value : null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
301
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
302
|
+
}
|