emdash 0.24.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 +12 -11
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +1 -1
- package/dist/api/schemas/index.mjs +2 -2
- package/dist/{api-d_0wi9fW.mjs → api-BMfveMoB.mjs} +24 -20
- package/dist/api-BMfveMoB.mjs.map +1 -0
- package/dist/{apply-BE9UaWX0.mjs → apply--IVNSdXn.mjs} +19 -16
- package/dist/apply--IVNSdXn.mjs.map +1 -0
- package/dist/astro/index.d.mts +9 -9
- package/dist/astro/index.mjs +3 -2
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +8 -8
- 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.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +99 -70
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -5
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -5
- 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 +6 -6
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +6 -6
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +6 -6
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +12 -12
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +12 -12
- package/dist/astro/routes/api/admin/bylines/index.mjs +12 -12
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +10 -10
- package/dist/astro/routes/api/admin/comments/_id_.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
- package/dist/astro/routes/api/admin/comments/counts.mjs +6 -6
- package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
- 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 +31 -31
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/index.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +32 -32
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +31 -31
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +32 -32
- package/dist/astro/routes/api/admin/plugins/updates.mjs +31 -31
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +31 -31
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +31 -31
- 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 +5 -5
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
- package/dist/astro/routes/api/admin/users/index.mjs +5 -5
- 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 +5 -5
- package/dist/astro/routes/api/auth/invite/index.mjs +5 -5
- package/dist/astro/routes/api/auth/invite/register-options.mjs +5 -5
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +6 -6
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/me.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -5
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +5 -5
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +5 -5
- package/dist/astro/routes/api/auth/passkey/verify.mjs +5 -5
- package/dist/astro/routes/api/auth/signup/complete.mjs +5 -5
- package/dist/astro/routes/api/auth/signup/request.mjs +6 -6
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +9 -9
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +9 -9
- 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 +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -6
- 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 +6 -6
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +11 -11
- 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 +6 -6
- package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/index.mjs +6 -6
- package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -6
- 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 +6 -6
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +8 -8
- package/dist/astro/routes/api/import/wordpress/execute.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/media.d.mts +13 -1
- package/dist/astro/routes/api/import/wordpress/media.d.mts.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +13 -9
- package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +8 -8
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -8
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +6 -6
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +9 -9
- package/dist/astro/routes/api/manifest.mjs +3 -3
- package/dist/astro/routes/api/mcp.mjs +36 -36
- package/dist/astro/routes/api/media/_id_/confirm.d.mts.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +44 -8
- package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +6 -6
- 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 +8 -8
- package/dist/astro/routes/api/media.mjs +22 -21
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +8 -8
- package/dist/astro/routes/api/menus/_name_/items.mjs +8 -8
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +8 -8
- package/dist/astro/routes/api/menus/_name_/translations.mjs +8 -8
- package/dist/astro/routes/api/menus/_name_.mjs +8 -8
- package/dist/astro/routes/api/menus/index.mjs +8 -8
- 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 +3 -3
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
- package/dist/astro/routes/api/redirects/404s/index.mjs +8 -8
- package/dist/astro/routes/api/redirects/404s/summary.mjs +8 -8
- package/dist/astro/routes/api/redirects/_id_.mjs +9 -9
- package/dist/astro/routes/api/redirects/index.mjs +9 -9
- 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 +31 -31
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +31 -31
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +31 -31
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +31 -31
- package/dist/astro/routes/api/schema/collections/index.mjs +31 -31
- package/dist/astro/routes/api/schema/index.mjs +6 -6
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +31 -31
- package/dist/astro/routes/api/schema/orphans/index.mjs +31 -31
- package/dist/astro/routes/api/search/enable.mjs +9 -9
- package/dist/astro/routes/api/search/index.mjs +8 -8
- package/dist/astro/routes/api/search/rebuild.mjs +9 -9
- package/dist/astro/routes/api/search/stats.mjs +6 -6
- package/dist/astro/routes/api/search/suggest.mjs +8 -8
- package/dist/astro/routes/api/sections/_slug_.mjs +8 -8
- package/dist/astro/routes/api/sections/index.mjs +8 -8
- package/dist/astro/routes/api/settings/email.mjs +4 -4
- package/dist/astro/routes/api/settings.mjs +11 -11
- package/dist/astro/routes/api/setup/admin-verify.mjs +5 -5
- package/dist/astro/routes/api/setup/admin.mjs +5 -5
- 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 +20 -20
- 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 +11 -11
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -11
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -11
- package/dist/astro/routes/api/taxonomies/index.mjs +11 -11
- 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 +6 -6
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +8 -8
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +8 -8
- package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
- package/dist/astro/routes/api/widget-areas/index.mjs +8 -8
- 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 +11 -10
- package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
- package/dist/astro/routes/sitemap.xml.mjs +6 -6
- package/dist/astro/types.d.mts +10 -10
- package/dist/{authorize-B__u-iZJ.mjs → authorize-BZnOGJFo.mjs} +2 -2
- package/dist/{authorize-B__u-iZJ.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/{byline-fields-Ca56gaT6.d.mts → byline-fields-KD3JFRuH.d.mts} +24 -6
- package/dist/byline-fields-KD3JFRuH.d.mts.map +1 -0
- package/dist/{byline-fields-B-KXkg5R.mjs → byline-fields-eyJMblSr.mjs} +2 -2
- package/dist/{byline-fields-B-KXkg5R.mjs.map → byline-fields-eyJMblSr.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-BMtJnPnG.mjs → bylines-DE47vP8d.mjs} +6 -6
- package/dist/{bylines-BMtJnPnG.mjs.map → bylines-DE47vP8d.mjs.map} +1 -1
- package/dist/{cache-BUJe2w6Z.mjs → cache-D9jF5bYw.mjs} +2 -2
- package/dist/{cache-BUJe2w6Z.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 +17 -17
- 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-PoVGKjp0.mjs → content-ncUWWjna.mjs} +10 -8
- package/dist/content-ncUWWjna.mjs.map +1 -0
- package/dist/{context-Dvws41_A.mjs → context-C_g-iJVA.mjs} +14 -8
- package/dist/context-C_g-iJVA.mjs.map +1 -0
- package/dist/{dashboard-DsYU8JKS.mjs → dashboard-CotyqQrW.mjs} +4 -4
- package/dist/{dashboard-DsYU8JKS.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/{db-errors-UXB1CK42.mjs → db-errors-CK46D-ly.mjs} +1 -1
- package/dist/{db-errors-UXB1CK42.mjs.map → db-errors-CK46D-ly.mjs.map} +1 -1
- package/dist/{placeholder-B0s2mWu5.mjs → enrich-BQ0mxJRs.mjs} +61 -10
- package/dist/enrich-BQ0mxJRs.mjs.map +1 -0
- package/dist/{error-lq_fL-RS.mjs → error-rVGiqWDt.mjs} +3 -2
- package/dist/error-rVGiqWDt.mjs.map +1 -0
- 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-BroXT-vD.d.mts → index-BRNabKrx.d.mts} +53 -15
- 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 +12 -12
- package/dist/index.mjs +43 -43
- 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-BR2SgfEc.mjs → loader-BA_zhkS_.mjs} +82 -19
- package/dist/loader-BA_zhkS_.mjs.map +1 -0
- package/dist/{manifest-schema-BWKnQYQF.mjs → manifest-schema-kPGX7VS-.mjs} +4 -1
- package/dist/manifest-schema-kPGX7VS-.mjs.map +1 -0
- package/dist/media/image-endpoint.mjs +1 -1
- package/dist/media/index.d.mts +45 -2
- package/dist/media/index.d.mts.map +1 -1
- package/dist/media/index.mjs +5 -3
- package/dist/media/index.mjs.map +1 -1
- package/dist/media/local-runtime.d.mts +9 -9
- package/dist/media/local-runtime.d.mts.map +1 -1
- package/dist/media/local-runtime.mjs +17 -5
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/{media-CTjJoJTh.mjs → media-C5t_eCnk.mjs} +4 -2
- package/dist/media-C5t_eCnk.mjs.map +1 -0
- package/dist/{media-allowlist-B77I5_bw.mjs → media-allowlist-Du8t1u6a.mjs} +2 -2
- package/dist/{media-allowlist-B77I5_bw.mjs.map → media-allowlist-Du8t1u6a.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-GvRSpgIA.mjs → menus-CYH09a41.mjs} +65 -41
- package/dist/menus-CYH09a41.mjs.map +1 -0
- package/dist/{mime-BTzvzJ8M.mjs → mime-CCEzze7W.mjs} +1 -1
- package/dist/{mime-BTzvzJ8M.mjs.map → mime-CCEzze7W.mjs.map} +1 -1
- package/dist/{normalize-ClbNIenm.mjs → normalize-CeglrBT9.mjs} +11 -2
- package/dist/normalize-CeglrBT9.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-CqECG0gM.d.mts → options-41nCWqi9.d.mts} +3 -3
- package/dist/{options-CqECG0gM.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-DHI873qU.mjs → parse-BwAdS7FZ.mjs} +2 -2
- package/dist/{parse-DHI873qU.mjs.map → parse-BwAdS7FZ.mjs.map} +1 -1
- package/dist/{placeholder-QoI4gEzP.d.mts → placeholder-Cuce9U-m.d.mts} +20 -5
- package/dist/placeholder-Cuce9U-m.d.mts.map +1 -0
- package/dist/plugin-types.d.mts +5 -2
- package/dist/plugin-types.d.mts.map +1 -1
- package/dist/plugin-utils.d.mts +8 -8
- package/dist/plugins/adapt-sandbox-entry.d.mts +8 -8
- package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
- package/dist/{query-DZdlS6Cu.mjs → query-BpQb-blG.mjs} +15 -15
- package/dist/{query-DZdlS6Cu.mjs.map → query-BpQb-blG.mjs.map} +1 -1
- package/dist/{rate-limit-D1Y6VFVP.mjs → rate-limit-Dap6HMNM.mjs} +2 -2
- package/dist/{rate-limit-D1Y6VFVP.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-x9eFPeyi.mjs → redirects-Dcbuisoj.mjs} +2 -1
- package/dist/redirects-Dcbuisoj.mjs.map +1 -0
- 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-CJkCubOJ.mjs → registry-g8yYt4cC.mjs} +14 -4
- package/dist/registry-g8yYt4cC.mjs.map +1 -0
- 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 +9 -9
- package/dist/runtime.mjs +2 -2
- package/dist/{schema-DMjXuvNU.mjs → schema-DoLd3j2e.mjs} +5 -5
- package/dist/{schema-DMjXuvNU.mjs.map → schema-DoLd3j2e.mjs.map} +1 -1
- package/dist/{search-D1wKk3lc.mjs → search-BR0ZeZmh.mjs} +4 -4
- package/dist/{search-D1wKk3lc.mjs.map → search-BR0ZeZmh.mjs.map} +1 -1
- package/dist/{sections-CCMic8dB.mjs → sections-5J2YJPqJ.mjs} +3 -3
- package/dist/{sections-CCMic8dB.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/index.d.mts +1 -1
- package/dist/{seo-DCEF-AVr.mjs → seo-BkhuuaaE.mjs} +25 -3
- package/dist/seo-BkhuuaaE.mjs.map +1 -0
- package/dist/{seo-3QrZ1EPT.mjs → seo-DKeXHkzE.mjs} +3 -3
- package/dist/{seo-3QrZ1EPT.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-C3Yi5Co3.mjs → settings-BMNr9Dmi.mjs} +3 -3
- package/dist/{settings-C3Yi5Co3.mjs.map → settings-BMNr9Dmi.mjs.map} +1 -1
- package/dist/{settings-D9rvaLuY.mjs → settings-DJeki-97.mjs} +4 -4
- package/dist/{settings-D9rvaLuY.mjs.map → settings-DJeki-97.mjs.map} +1 -1
- package/dist/{taxonomies-DhLaNJji.mjs → taxonomies-CM_fUqLU.mjs} +5 -4
- package/dist/taxonomies-CM_fUqLU.mjs.map +1 -0
- package/dist/{taxonomies-CurpLtNC.mjs → taxonomies-D13i2ctt.mjs} +76 -21
- 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-TTx8uP7V.d.mts → types-BYnDBVwe.d.mts} +27 -5
- package/dist/{types-TTx8uP7V.d.mts.map → types-BYnDBVwe.d.mts.map} +1 -1
- package/dist/{types-CTLJYMhN.d.mts → types-CNlaBFzx.d.mts} +11 -1
- package/dist/{types-CTLJYMhN.d.mts.map → types-CNlaBFzx.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-BWh2MjmA.d.mts → types-ETmO_jQr.d.mts} +2 -2
- package/dist/{types-BWh2MjmA.d.mts.map → types-ETmO_jQr.d.mts.map} +1 -1
- package/dist/types-tM44hEcf.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-DOFrIb-c.d.mts → validate-CG0V0wNR.d.mts} +3 -3
- package/dist/{validate-DOFrIb-c.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-CY1gk4Ar.mjs → validation-CwUfTdGF.mjs} +6 -6
- package/dist/{validation-CY1gk4Ar.mjs.map → validation-CwUfTdGF.mjs.map} +1 -1
- package/dist/version-BoNBItP_.mjs +7 -0
- package/dist/{version-C0JPFUG8.mjs.map → version-BoNBItP_.mjs.map} +1 -1
- package/dist/{widgets-Dri0spI4.mjs → widgets-BfOZ-ajY.mjs} +2 -2
- package/dist/{widgets-Dri0spI4.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 +6 -6
- package/src/api/errors.ts +1 -0
- package/src/api/handlers/content.ts +4 -4
- package/src/api/handlers/seo.ts +30 -2
- package/src/api/handlers/taxonomies.ts +5 -1
- package/src/api/schemas/schema.ts +1 -0
- package/src/astro/integration/virtual-modules.ts +15 -0
- package/src/astro/middleware.ts +14 -1
- package/src/astro/routes/api/import/wordpress/media.ts +9 -3
- package/src/astro/routes/api/media/[id]/confirm.ts +62 -2
- package/src/astro/routes/api/media.ts +17 -19
- package/src/astro/routes/sitemap-[collection].xml.ts +5 -4
- package/src/components/EmDashHead.astro +23 -3
- package/src/components/EmDashImage.astro +4 -7
- package/src/components/Image.astro +14 -4
- package/src/components/InlinePortableTextEditor.tsx +48 -1
- 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/content.ts +8 -5
- package/src/database/repositories/media-usage.ts +444 -0
- package/src/database/repositories/media.ts +9 -1
- package/src/database/types.ts +39 -0
- package/src/emdash-runtime.ts +72 -25
- package/src/index.ts +3 -0
- package/src/loader.ts +110 -31
- package/src/media/enrich.ts +78 -0
- package/src/media/index.ts +1 -0
- package/src/media/local-runtime.ts +19 -0
- package/src/media/normalize.ts +20 -1
- package/src/media/placeholder.ts +29 -9
- package/src/media/types.ts +14 -0
- 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 +12 -0
- package/src/plugins/context.ts +8 -0
- package/src/plugins/define-plugin.ts +14 -5
- package/src/plugins/hooks.ts +66 -33
- package/src/plugins/index.ts +6 -0
- package/src/plugins/manager.ts +33 -0
- package/src/plugins/manifest-schema.ts +3 -0
- package/src/plugins/types.ts +41 -2
- package/src/schema/registry.ts +25 -0
- package/src/schema/types.ts +10 -0
- package/src/schema/zod-generator.ts +9 -1
- package/src/seed/apply.ts +7 -0
- package/src/taxonomies/index.ts +121 -18
- package/dist/api-d_0wi9fW.mjs.map +0 -1
- package/dist/apply-BE9UaWX0.mjs.map +0 -1
- package/dist/byline-fields-Ca56gaT6.d.mts.map +0 -1
- package/dist/content-PoVGKjp0.mjs.map +0 -1
- package/dist/context-Dvws41_A.mjs.map +0 -1
- package/dist/error-lq_fL-RS.mjs.map +0 -1
- package/dist/index-BroXT-vD.d.mts.map +0 -1
- package/dist/loader-BR2SgfEc.mjs.map +0 -1
- package/dist/manifest-schema-BWKnQYQF.mjs.map +0 -1
- package/dist/media-CTjJoJTh.mjs.map +0 -1
- package/dist/menus-GvRSpgIA.mjs.map +0 -1
- package/dist/normalize-ClbNIenm.mjs.map +0 -1
- package/dist/object-cache-SEb2IM4Z.mjs.map +0 -1
- package/dist/placeholder-B0s2mWu5.mjs.map +0 -1
- package/dist/placeholder-QoI4gEzP.d.mts.map +0 -1
- package/dist/redirects-x9eFPeyi.mjs.map +0 -1
- package/dist/registry-CJkCubOJ.mjs.map +0 -1
- package/dist/runner-BsquExlK.mjs.map +0 -1
- package/dist/seo-DCEF-AVr.mjs.map +0 -1
- package/dist/taxonomies-CurpLtNC.mjs.map +0 -1
- package/dist/taxonomies-DhLaNJji.mjs.map +0 -1
- package/dist/types-B-gIxFCE.d.mts.map +0 -1
- package/dist/version-C0JPFUG8.mjs +0 -7
- package/dist/zod-generator-xEHODMJf.mjs.map +0 -1
package/src/database/types.ts
CHANGED
|
@@ -60,6 +60,43 @@ export interface MediaTable {
|
|
|
60
60
|
author_id: string | null;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export interface MediaUsageSourceTable {
|
|
64
|
+
source_key: string;
|
|
65
|
+
source_type: string;
|
|
66
|
+
collection_slug: string | null;
|
|
67
|
+
content_id: string | null;
|
|
68
|
+
source_variant: string;
|
|
69
|
+
locale: string | null;
|
|
70
|
+
translation_group: string | null;
|
|
71
|
+
content_slug: string | null;
|
|
72
|
+
content_title: string | null;
|
|
73
|
+
content_status: string | null;
|
|
74
|
+
content_scheduled_at: string | null;
|
|
75
|
+
content_deleted_at: string | null;
|
|
76
|
+
revision_id: string | null;
|
|
77
|
+
current_generation: string;
|
|
78
|
+
schema_version: Generated<number>;
|
|
79
|
+
indexed_at: Generated<string>;
|
|
80
|
+
created_at: Generated<string>;
|
|
81
|
+
updated_at: Generated<string>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface MediaUsageTable {
|
|
85
|
+
id: string;
|
|
86
|
+
source_key: string;
|
|
87
|
+
generation: string;
|
|
88
|
+
field_slug: string;
|
|
89
|
+
field_path: string;
|
|
90
|
+
occurrence_index: Generated<number>;
|
|
91
|
+
reference_type: string;
|
|
92
|
+
media_id: string | null;
|
|
93
|
+
provider: Generated<string>;
|
|
94
|
+
provider_asset_id: string;
|
|
95
|
+
media_kind: string | null;
|
|
96
|
+
mime_type: string | null;
|
|
97
|
+
created_at: Generated<string>;
|
|
98
|
+
}
|
|
99
|
+
|
|
63
100
|
export interface UserTable {
|
|
64
101
|
id: string;
|
|
65
102
|
email: string;
|
|
@@ -414,6 +451,8 @@ export interface Database {
|
|
|
414
451
|
content_taxonomies: ContentTaxonomyTable;
|
|
415
452
|
_emdash_taxonomy_defs: TaxonomyDefTable;
|
|
416
453
|
media: MediaTable;
|
|
454
|
+
_emdash_media_usage_sources: MediaUsageSourceTable;
|
|
455
|
+
_emdash_media_usage: MediaUsageTable;
|
|
417
456
|
users: UserTable;
|
|
418
457
|
credentials: CredentialTable;
|
|
419
458
|
auth_tokens: AuthTokenTable;
|
package/src/emdash-runtime.ts
CHANGED
|
@@ -2797,7 +2797,14 @@ export class EmDashRuntime {
|
|
|
2797
2797
|
}
|
|
2798
2798
|
|
|
2799
2799
|
async handleContentRestore(collection: string, id: string) {
|
|
2800
|
-
|
|
2800
|
+
const result = await handleContentRestore(this.db, collection, id);
|
|
2801
|
+
|
|
2802
|
+
// Run afterRestore hooks (fire-and-forget)
|
|
2803
|
+
if (result.success) {
|
|
2804
|
+
this.runAfterRestoreHooks(contentItemToRecord(result.data.item), collection);
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
return result;
|
|
2801
2808
|
}
|
|
2802
2809
|
|
|
2803
2810
|
async handleContentPermanentDelete(collection: string, id: string) {
|
|
@@ -2850,11 +2857,25 @@ export class EmDashRuntime {
|
|
|
2850
2857
|
}
|
|
2851
2858
|
|
|
2852
2859
|
async handleContentSchedule(collection: string, id: string, scheduledAt: string) {
|
|
2853
|
-
|
|
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;
|
|
2854
2868
|
}
|
|
2855
2869
|
|
|
2856
2870
|
async handleContentUnschedule(collection: string, id: string) {
|
|
2857
|
-
|
|
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;
|
|
2858
2879
|
}
|
|
2859
2880
|
|
|
2860
2881
|
async handleContentCountScheduled(collection: string) {
|
|
@@ -3313,14 +3334,41 @@ export class EmDashRuntime {
|
|
|
3313
3334
|
}
|
|
3314
3335
|
}
|
|
3315
3336
|
|
|
3316
|
-
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
|
+
|
|
3317
3349
|
after(async () => {
|
|
3318
3350
|
// Trusted plugins
|
|
3319
|
-
if (this.hooks.hasHooks(
|
|
3351
|
+
if (this.hooks.hasHooks(name)) {
|
|
3320
3352
|
try {
|
|
3321
|
-
|
|
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
|
+
}
|
|
3322
3370
|
} catch (err) {
|
|
3323
|
-
console.error(
|
|
3371
|
+
console.error(`EmDash ${label} hook error:`, err);
|
|
3324
3372
|
}
|
|
3325
3373
|
}
|
|
3326
3374
|
|
|
@@ -3333,9 +3381,9 @@ export class EmDashRuntime {
|
|
|
3333
3381
|
tasks.push(
|
|
3334
3382
|
(async () => {
|
|
3335
3383
|
try {
|
|
3336
|
-
await plugin.invokeHook(
|
|
3384
|
+
await plugin.invokeHook(name, { content, collection });
|
|
3337
3385
|
} catch (err) {
|
|
3338
|
-
console.error(`EmDash: Sandboxed plugin ${pluginId}
|
|
3386
|
+
console.error(`EmDash: Sandboxed plugin ${pluginId} ${label} error:`, err);
|
|
3339
3387
|
}
|
|
3340
3388
|
})(),
|
|
3341
3389
|
);
|
|
@@ -3344,25 +3392,24 @@ export class EmDashRuntime {
|
|
|
3344
3392
|
});
|
|
3345
3393
|
}
|
|
3346
3394
|
|
|
3395
|
+
private runAfterPublishHooks(content: Record<string, unknown>, collection: string): void {
|
|
3396
|
+
this.runDeferredContentHook("content:afterPublish", content, collection);
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3347
3399
|
private runAfterUnpublishHooks(content: Record<string, unknown>, collection: string): void {
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
this.hooks
|
|
3351
|
-
.runContentAfterUnpublish(content, collection)
|
|
3352
|
-
.catch((err) => console.error("EmDash afterUnpublish hook error:", err));
|
|
3353
|
-
}
|
|
3400
|
+
this.runDeferredContentHook("content:afterUnpublish", content, collection);
|
|
3401
|
+
}
|
|
3354
3402
|
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
if (!pluginId || !this.isPluginEnabled(pluginId)) continue;
|
|
3403
|
+
private runAfterRestoreHooks(content: Record<string, unknown>, collection: string): void {
|
|
3404
|
+
this.runDeferredContentHook("content:afterRestore", content, collection);
|
|
3405
|
+
}
|
|
3359
3406
|
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3407
|
+
private runAfterScheduleHooks(content: Record<string, unknown>, collection: string): void {
|
|
3408
|
+
this.runDeferredContentHook("content:afterSchedule", content, collection);
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
private runAfterUnscheduleHooks(content: Record<string, unknown>, collection: string): void {
|
|
3412
|
+
this.runDeferredContentHook("content:afterUnschedule", content, collection);
|
|
3366
3413
|
}
|
|
3367
3414
|
|
|
3368
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,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Metadata Enrichment
|
|
3
|
+
*
|
|
4
|
+
* Single seam that derives image dimensions and LQIP placeholders (blurhash,
|
|
5
|
+
* dominant color) from raw image bytes. Every server-side media-creation path
|
|
6
|
+
* routes through this so records are populated consistently. Pure-JS and
|
|
7
|
+
* Workers-safe (image-size reads headers only; generatePlaceholder guards
|
|
8
|
+
* decode size).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { normalizeMime } from "./mime.js";
|
|
12
|
+
import { generatePlaceholder, readDimensions } from "./placeholder.js";
|
|
13
|
+
|
|
14
|
+
export interface EnrichedImageMetadata {
|
|
15
|
+
width?: number;
|
|
16
|
+
height?: number;
|
|
17
|
+
blurhash?: string;
|
|
18
|
+
dominantColor?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Derive dimensions + LQIP placeholders from image bytes.
|
|
23
|
+
*
|
|
24
|
+
* - Non-image content types return `{}`.
|
|
25
|
+
* - `knownDimensions` (e.g. browser `naturalWidth/Height`) win over `image-size`
|
|
26
|
+
* for the *stored record* because the browser applies EXIF orientation;
|
|
27
|
+
* `image-size` reports raw header dimensions, which are swapped for
|
|
28
|
+
* 90°/270°-rotated JPEGs. They are NOT used for the decode OOM guard — see below.
|
|
29
|
+
* - The placeholder OOM guard uses only header dimensions read from the bytes
|
|
30
|
+
* actually decoded. Caller-supplied `knownDimensions` are untrusted for the
|
|
31
|
+
* guard: a client could claim a tiny size for a huge image to bypass the cap.
|
|
32
|
+
* - `placeholder` lets a caller decode a smaller thumbnail for the blurhash to
|
|
33
|
+
* avoid OOM on large originals; dimensions still come from `bytes`.
|
|
34
|
+
* - Placeholders are jpeg/png only (the generator's supported formats); other
|
|
35
|
+
* image types still get dimensions.
|
|
36
|
+
*/
|
|
37
|
+
export async function enrichImageMetadata(
|
|
38
|
+
bytes: Uint8Array,
|
|
39
|
+
contentType: string,
|
|
40
|
+
opts?: {
|
|
41
|
+
knownDimensions?: { width: number; height: number };
|
|
42
|
+
placeholder?: { bytes: Uint8Array; contentType: string };
|
|
43
|
+
},
|
|
44
|
+
): Promise<EnrichedImageMetadata> {
|
|
45
|
+
const normalizedContentType = normalizeMime(contentType);
|
|
46
|
+
if (!normalizedContentType.startsWith("image/")) return {};
|
|
47
|
+
|
|
48
|
+
// Header dimensions are read once from the actual bytes. They feed the
|
|
49
|
+
// placeholder OOM guard, which must never trust caller-supplied dimensions:
|
|
50
|
+
// `knownDimensions` is decoupled from the buffer, so a client could claim a
|
|
51
|
+
// tiny size for a huge image and slip past the decoded-size cap, making the
|
|
52
|
+
// decoder allocate an unbounded RGBA buffer and OOM the runtime. Only dims
|
|
53
|
+
// read from the buffer that actually gets decoded can bound the decode.
|
|
54
|
+
const headerDims = readDimensions(bytes) ?? undefined;
|
|
55
|
+
|
|
56
|
+
// Dimensions published on the record prefer the caller's knownDimensions
|
|
57
|
+
// (e.g. browser naturalWidth/Height, which apply EXIF orientation) over the
|
|
58
|
+
// raw header dims, which are swapped for 90°/270°-rotated JPEGs.
|
|
59
|
+
const recordDims = opts?.knownDimensions ?? headerDims;
|
|
60
|
+
|
|
61
|
+
// When a smaller thumbnail override is supplied, decode that for the blurhash
|
|
62
|
+
// and let generatePlaceholder read the thumbnail's own header for the OOM
|
|
63
|
+
// guard (the override buffer is what actually gets decoded). On the common
|
|
64
|
+
// no-override path pass the header dims already read from this same buffer.
|
|
65
|
+
const override = opts?.placeholder;
|
|
66
|
+
const placeholder = await generatePlaceholder(
|
|
67
|
+
override ? override.bytes : bytes,
|
|
68
|
+
override ? normalizeMime(override.contentType) : normalizedContentType,
|
|
69
|
+
override ? undefined : headerDims,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
width: recordDims?.width,
|
|
74
|
+
height: recordDims?.height,
|
|
75
|
+
blurhash: placeholder?.blurhash,
|
|
76
|
+
dominantColor: placeholder?.dominantColor,
|
|
77
|
+
};
|
|
78
|
+
}
|
package/src/media/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type {
|
|
|
27
27
|
export { mediaItemToValue } from "./types.js";
|
|
28
28
|
export { normalizeMediaValue } from "./normalize.js";
|
|
29
29
|
export { generatePlaceholder, type PlaceholderData } from "./placeholder.js";
|
|
30
|
+
export { enrichImageMetadata, type EnrichedImageMetadata } from "./enrich.js";
|
|
30
31
|
|
|
31
32
|
// Built-in providers
|
|
32
33
|
export { localMedia, type LocalMediaConfig } from "./local.js";
|
|
@@ -73,6 +73,8 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
|
|
|
73
73
|
size: item.size ?? undefined,
|
|
74
74
|
width: item.width ?? undefined,
|
|
75
75
|
height: item.height ?? undefined,
|
|
76
|
+
blurhash: item.blurhash ?? undefined,
|
|
77
|
+
dominantColor: item.dominantColor ?? undefined,
|
|
76
78
|
alt: item.alt ?? undefined,
|
|
77
79
|
previewUrl: `/_emdash/api/media/file/${item.storageKey}`,
|
|
78
80
|
meta: {
|
|
@@ -97,6 +99,8 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
|
|
|
97
99
|
size: item.size ?? undefined,
|
|
98
100
|
width: item.width ?? undefined,
|
|
99
101
|
height: item.height ?? undefined,
|
|
102
|
+
blurhash: item.blurhash ?? undefined,
|
|
103
|
+
dominantColor: item.dominantColor ?? undefined,
|
|
100
104
|
alt: item.alt ?? undefined,
|
|
101
105
|
previewUrl: `/_emdash/api/media/file/${item.storageKey}`,
|
|
102
106
|
meta: {
|
|
@@ -148,6 +152,15 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
|
|
|
148
152
|
const src = `/_emdash/api/media/file/${storageKey}`;
|
|
149
153
|
const mimeType = value.mimeType || "";
|
|
150
154
|
|
|
155
|
+
// Prefer the first-class fields; fall back to `meta` for legacy snapshots
|
|
156
|
+
// stored before LQIP was promoted off the provider-specific `meta` bag.
|
|
157
|
+
const blurhash =
|
|
158
|
+
value.blurhash ??
|
|
159
|
+
(typeof value.meta?.blurhash === "string" ? value.meta.blurhash : undefined);
|
|
160
|
+
const dominantColor =
|
|
161
|
+
value.dominantColor ??
|
|
162
|
+
(typeof value.meta?.dominantColor === "string" ? value.meta.dominantColor : undefined);
|
|
163
|
+
|
|
151
164
|
// Determine embed type based on MIME type
|
|
152
165
|
if (mimeType.startsWith("image/")) {
|
|
153
166
|
return {
|
|
@@ -155,6 +168,8 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
|
|
|
155
168
|
src,
|
|
156
169
|
width: value.width,
|
|
157
170
|
height: value.height,
|
|
171
|
+
blurhash,
|
|
172
|
+
dominantColor,
|
|
158
173
|
alt: value.alt,
|
|
159
174
|
};
|
|
160
175
|
}
|
|
@@ -185,6 +200,8 @@ export const createMediaProvider: CreateMediaProviderFn<LocalMediaRuntimeConfig>
|
|
|
185
200
|
src,
|
|
186
201
|
width: value.width,
|
|
187
202
|
height: value.height,
|
|
203
|
+
blurhash,
|
|
204
|
+
dominantColor,
|
|
188
205
|
alt: value.alt,
|
|
189
206
|
};
|
|
190
207
|
},
|
|
@@ -221,6 +238,8 @@ export function repoItemToProviderItem(item: {
|
|
|
221
238
|
size: item.size ?? undefined,
|
|
222
239
|
width: item.width ?? undefined,
|
|
223
240
|
height: item.height ?? undefined,
|
|
241
|
+
blurhash: item.blurhash ?? undefined,
|
|
242
|
+
dominantColor: item.dominantColor ?? undefined,
|
|
224
243
|
alt: item.alt ?? undefined,
|
|
225
244
|
previewUrl: `/_emdash/api/media/file/${item.storageKey}`,
|
|
226
245
|
meta: {
|
package/src/media/normalize.ts
CHANGED
|
@@ -60,7 +60,14 @@ export async function normalizeMediaValue(
|
|
|
60
60
|
const needsDimensions = result.width == null || result.height == null;
|
|
61
61
|
const needsStorageKey = provider === "local" && !result.meta?.storageKey;
|
|
62
62
|
const needsFileInfo = !result.mimeType || !result.filename;
|
|
63
|
-
|
|
63
|
+
// LQIP placeholders are immutable facts of the bytes: if an image record is
|
|
64
|
+
// missing them, the provider may have gained them since (e.g. content saved
|
|
65
|
+
// before LQIP backfill ran, or the row's blurhash was populated later). Pull
|
|
66
|
+
// them on every image lookup so the LQIP backfill in mergeProviderData runs.
|
|
67
|
+
const needsLqip =
|
|
68
|
+
(result.mimeType ?? "").startsWith("image/") &&
|
|
69
|
+
(result.blurhash == null || result.dominantColor == null);
|
|
70
|
+
const needsLookup = needsDimensions || needsStorageKey || needsFileInfo || needsLqip;
|
|
64
71
|
|
|
65
72
|
if (!needsLookup || !id) return result;
|
|
66
73
|
|
|
@@ -138,6 +145,8 @@ async function resolveInternalUrl(
|
|
|
138
145
|
mimeType: item.mimeType,
|
|
139
146
|
width: item.width,
|
|
140
147
|
height: item.height,
|
|
148
|
+
blurhash: item.blurhash,
|
|
149
|
+
dominantColor: item.dominantColor,
|
|
141
150
|
alt: item.alt,
|
|
142
151
|
meta: item.meta,
|
|
143
152
|
};
|
|
@@ -167,6 +176,8 @@ async function resolveLocalId(
|
|
|
167
176
|
mimeType: item.mimeType,
|
|
168
177
|
width: item.width,
|
|
169
178
|
height: item.height,
|
|
179
|
+
blurhash: item.blurhash,
|
|
180
|
+
dominantColor: item.dominantColor,
|
|
170
181
|
alt: item.alt,
|
|
171
182
|
meta: item.meta,
|
|
172
183
|
};
|
|
@@ -183,6 +194,12 @@ function mergeProviderData(existing: MediaValue, item: MediaProviderItem): Media
|
|
|
183
194
|
if (result.width == null && item.width != null) result.width = item.width;
|
|
184
195
|
if (result.height == null && item.height != null) result.height = item.height;
|
|
185
196
|
|
|
197
|
+
// Fill missing LQIP placeholders (immutable facts of the bytes; caller wins)
|
|
198
|
+
if (result.blurhash == null && item.blurhash != null) result.blurhash = item.blurhash;
|
|
199
|
+
if (result.dominantColor == null && item.dominantColor != null) {
|
|
200
|
+
result.dominantColor = item.dominantColor;
|
|
201
|
+
}
|
|
202
|
+
|
|
186
203
|
// Fill missing file info
|
|
187
204
|
if (!result.filename && item.filename) result.filename = item.filename;
|
|
188
205
|
if (!result.mimeType && item.mimeType) result.mimeType = item.mimeType;
|
|
@@ -217,6 +234,8 @@ function recordToMediaValue(obj: Record<string, unknown>): MediaValue {
|
|
|
217
234
|
if (typeof obj.mimeType === "string") result.mimeType = obj.mimeType;
|
|
218
235
|
if (typeof obj.width === "number") result.width = obj.width;
|
|
219
236
|
if (typeof obj.height === "number") result.height = obj.height;
|
|
237
|
+
if (typeof obj.blurhash === "string") result.blurhash = obj.blurhash;
|
|
238
|
+
if (typeof obj.dominantColor === "string") result.dominantColor = obj.dominantColor;
|
|
220
239
|
if (typeof obj.alt === "string") result.alt = obj.alt;
|
|
221
240
|
if (isRecord(obj.meta)) result.meta = obj.meta;
|
|
222
241
|
return result;
|