emdash 0.9.0 → 0.11.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/{adapters-DoNJiveC.d.mts → adapters-BktHA7EO.d.mts} +1 -1
- package/dist/{adapters-DoNJiveC.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
- package/dist/{apply-BzltprvY.mjs → apply-Ded_1vng.mjs} +167 -254
- package/dist/apply-Ded_1vng.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.mjs +10 -2
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +5 -5
- package/dist/astro/middleware/auth.mjs +5 -5
- package/dist/astro/middleware/redirect.mjs +5 -5
- package/dist/astro/middleware/request-context.mjs +4 -4
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +94 -43
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +12 -11
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{base64-BRICGH2l.mjs → base64-MBPo9ozB.mjs} +1 -1
- package/dist/{base64-BRICGH2l.mjs.map → base64-MBPo9ozB.mjs.map} +1 -1
- package/dist/{byline-BSaNL1w7.mjs → byline-gFn1r0vA.mjs} +4 -4
- package/dist/{byline-BSaNL1w7.mjs.map → byline-gFn1r0vA.mjs.map} +1 -1
- package/dist/{bylines-CvJ3PYz2.mjs → bylines-DTFI8nDM.mjs} +5 -5
- package/dist/{bylines-CvJ3PYz2.mjs.map → bylines-DTFI8nDM.mjs.map} +1 -1
- package/dist/{cache-C6N_hhN7.mjs → cache-BAJbeoZ8.mjs} +3 -3
- package/dist/{cache-C6N_hhN7.mjs.map → cache-BAJbeoZ8.mjs.map} +1 -1
- package/dist/{chunks-NBQVDOci.mjs → chunks-BK1oZS-l.mjs} +2 -2
- package/dist/{chunks-NBQVDOci.mjs.map → chunks-BK1oZS-l.mjs.map} +1 -1
- package/dist/cli/index.mjs +342 -95
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/{config-BI0V3ICQ.mjs → config-CVssduLe.mjs} +1 -1
- package/dist/{config-BI0V3ICQ.mjs.map → config-CVssduLe.mjs.map} +1 -1
- package/dist/{content-8lOYF0pr.mjs → content-CERxPUN0.mjs} +14 -3
- package/dist/content-CERxPUN0.mjs.map +1 -0
- package/dist/database/instrumentation.d.mts +6 -4
- package/dist/database/instrumentation.d.mts.map +1 -1
- package/dist/database/instrumentation.mjs +19 -7
- package/dist/database/instrumentation.mjs.map +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-WRezodiz.mjs → db-errors-B7P2pSCn.mjs} +1 -1
- package/dist/{db-errors-WRezodiz.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
- package/dist/{default-D8ksjWhO.mjs → default-pHuz9WF6.mjs} +1 -1
- package/dist/{default-D8ksjWhO.mjs.map → default-pHuz9WF6.mjs.map} +1 -1
- package/dist/{error-D_-tqP-I.mjs → error-DqnRMM5z.mjs} +1 -1
- package/dist/{error-D_-tqP-I.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
- package/dist/{index-BFRaVcD6.d.mts → index-Cg-rC4Gj.d.mts} +110 -87
- package/dist/index-Cg-rC4Gj.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +29 -28
- package/dist/{load-DDqMMvZL.mjs → load-DR1VwFXR.mjs} +2 -2
- package/dist/{load-DDqMMvZL.mjs.map → load-DR1VwFXR.mjs.map} +1 -1
- package/dist/{loader-CKLbBnhK.mjs → loader-ou_PXAjg.mjs} +31 -6
- package/dist/loader-ou_PXAjg.mjs.map +1 -0
- package/dist/{manifest-schema-DqWNC3lM.mjs → manifest-schema-CXAbd1vH.mjs} +1 -1
- package/dist/{manifest-schema-DqWNC3lM.mjs.map → manifest-schema-CXAbd1vH.mjs.map} +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +3 -3
- package/dist/{media-BW32b4gi.mjs → media-1fFhub9c.mjs} +22 -10
- package/dist/media-1fFhub9c.mjs.map +1 -0
- package/dist/{mode-ier8jbBk.mjs → mode-YhqNVef_.mjs} +1 -1
- package/dist/{mode-ier8jbBk.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
- package/dist/{options-BVp3UsTS.mjs → options-nPxWnrya.mjs} +1 -1
- package/dist/{options-BVp3UsTS.mjs.map → options-nPxWnrya.mjs.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
- package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
- package/dist/{placeholder-BE4o_2dc.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
- package/dist/{placeholder-BE4o_2dc.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
- package/dist/{placeholder-CIJejMlK.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
- package/dist/{placeholder-CIJejMlK.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
- package/dist/{public-url-DByxYjUw.mjs → public-url-B1AxbbbQ.mjs} +1 -1
- package/dist/{public-url-DByxYjUw.mjs.map → public-url-B1AxbbbQ.mjs.map} +1 -1
- package/dist/{query-Cg9ZKRQ0.mjs → query-8c_meo_K.mjs} +13 -13
- package/dist/{query-Cg9ZKRQ0.mjs.map → query-8c_meo_K.mjs.map} +1 -1
- package/dist/{redirect-BhUBKRc1.mjs → redirect-C5H7VGIX.mjs} +3 -3
- package/dist/{redirect-BhUBKRc1.mjs.map → redirect-C5H7VGIX.mjs.map} +1 -1
- package/dist/{registry-Dw70ChxB.mjs → registry-Do34mz_P.mjs} +7 -6
- package/dist/registry-Do34mz_P.mjs.map +1 -0
- package/dist/{request-cache-B-bmkipQ.mjs → request-cache-D4I69LeL.mjs} +6 -2
- package/dist/request-cache-D4I69LeL.mjs.map +1 -0
- package/dist/request-context.d.mts +27 -1
- package/dist/request-context.d.mts.map +1 -1
- package/dist/request-context.mjs +16 -3
- package/dist/request-context.mjs.map +1 -1
- package/dist/{runner-C7ADox5q.mjs → runner-DIcU2UCC.mjs} +465 -148
- package/dist/runner-DIcU2UCC.mjs.map +1 -0
- package/dist/{runner-Bnoj7vjK.d.mts → runner-Iu3IZSDM.d.mts} +2 -2
- package/dist/{runner-Bnoj7vjK.d.mts.map → runner-Iu3IZSDM.d.mts.map} +1 -1
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +3 -3
- package/dist/{search-dOGEccMa.mjs → search-DuWhx4NG.mjs} +322 -108
- package/dist/search-DuWhx4NG.mjs.map +1 -0
- package/dist/{secrets-CW3reAnU.mjs → secrets-CZ8rxLX3.mjs} +3 -3
- package/dist/{secrets-CW3reAnU.mjs.map → secrets-CZ8rxLX3.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +15 -14
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/taxonomies-Bw76xAxo.mjs +407 -0
- package/dist/taxonomies-Bw76xAxo.mjs.map +1 -0
- package/dist/taxonomy-D6NvlKo8.mjs +218 -0
- package/dist/taxonomy-D6NvlKo8.mjs.map +1 -0
- package/dist/{tokens-D7zMmWi2.mjs → tokens-CyRDPVW2.mjs} +2 -2
- package/dist/{tokens-D7zMmWi2.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
- package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
- package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
- package/dist/{transport-DNEfeMaU.d.mts → transport-DX_5rpsq.d.mts} +1 -1
- package/dist/{transport-DNEfeMaU.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
- package/dist/{transport-BeMCmin1.mjs → transport-xpzIjCIB.mjs} +1 -1
- package/dist/{transport-BeMCmin1.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
- package/dist/{types-CIOg5AR8.mjs → types-56BKbld_.mjs} +1 -1
- package/dist/types-56BKbld_.mjs.map +1 -0
- package/dist/{types-CRxNbK-Z.mjs → types-BIgulNsW.mjs} +2 -2
- package/dist/{types-CRxNbK-Z.mjs.map → types-BIgulNsW.mjs.map} +1 -1
- package/dist/{types-CrtWgIvl.d.mts → types-BQx6ZXpR.d.mts} +10 -1
- package/dist/types-BQx6ZXpR.d.mts.map +1 -0
- package/dist/{types-CJsYGpco.d.mts → types-B_CXXnzh.d.mts} +1 -1
- package/dist/{types-CJsYGpco.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
- package/dist/{types-M78DQ1lx.d.mts → types-C-aFbqmA.d.mts} +1 -1
- package/dist/{types-M78DQ1lx.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
- package/dist/types-DiI8NOG_.mjs +16 -0
- package/dist/types-DiI8NOG_.mjs.map +1 -0
- package/dist/{types-BuBIptGk.d.mts → types-IN5z_S3P.d.mts} +158 -92
- package/dist/types-IN5z_S3P.d.mts.map +1 -0
- package/dist/{types-BSyXeCFW.d.mts → types-IZSZfEwv.d.mts} +4 -3
- package/dist/types-IZSZfEwv.d.mts.map +1 -0
- package/dist/{types-CDbKp7ND.mjs → types-K-EkEQCI.mjs} +1 -1
- package/dist/{types-CDbKp7ND.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
- package/dist/{validate-BfQh_C_y.d.mts → validate-CO3JjFV5.d.mts} +22 -5
- package/dist/validate-CO3JjFV5.d.mts.map +1 -0
- package/dist/{validate-Baqf0slj.mjs → validate-UK4Ja1uo.mjs} +14 -10
- package/dist/validate-UK4Ja1uo.mjs.map +1 -0
- package/dist/{validation-BfEI7tNe.mjs → validation-Vc5DQkJa.mjs} +5 -5
- package/dist/{validation-BfEI7tNe.mjs.map → validation-Vc5DQkJa.mjs.map} +1 -1
- package/dist/version-Bg31I_Ff.mjs +7 -0
- package/dist/{version-DoxrVdYf.mjs.map → version-Bg31I_Ff.mjs.map} +1 -1
- package/dist/{zod-generator-CC0xNe_K.mjs → zod-generator-CHnJUP2l.mjs} +8 -3
- package/dist/zod-generator-CHnJUP2l.mjs.map +1 -0
- package/package.json +9 -8
- package/src/api/errors.ts +5 -0
- package/src/api/handlers/content.ts +20 -0
- package/src/api/handlers/dashboard.ts +29 -36
- package/src/api/handlers/media-allowlist.ts +40 -0
- package/src/api/handlers/media.ts +1 -1
- package/src/api/handlers/menus.ts +400 -89
- package/src/api/handlers/taxonomies.ts +273 -97
- package/src/api/handlers/validate-media-fields.ts +125 -0
- package/src/api/schemas/common.ts +7 -0
- package/src/api/schemas/media.ts +23 -3
- package/src/api/schemas/menus.ts +23 -0
- package/src/api/schemas/schema.ts +11 -2
- package/src/api/schemas/taxonomies.ts +39 -0
- package/src/astro/integration/routes.ts +10 -0
- package/src/astro/middleware.ts +46 -11
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +1 -1
- package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
- package/src/astro/routes/api/media/upload-url.ts +10 -4
- package/src/astro/routes/api/media.ts +12 -4
- package/src/astro/routes/api/menus/[name]/items.ts +16 -6
- package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
- package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
- package/src/astro/routes/api/menus/[name].ts +19 -10
- package/src/astro/routes/api/menus/index.ts +9 -6
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
- package/src/astro/routes/api/taxonomies/index.ts +9 -6
- package/src/astro/types.ts +5 -1
- package/src/auth/rate-limit.ts +3 -3
- package/src/cli/commands/bundle-utils.ts +81 -6
- package/src/cli/commands/bundle.ts +18 -15
- package/src/cli/commands/export-seed.ts +139 -24
- package/src/cli/commands/plugin-init.ts +216 -90
- package/src/database/instrumentation.ts +22 -8
- package/src/database/migrations/016_api_tokens.ts +18 -3
- package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +477 -0
- package/src/database/migrations/037_credential_algorithm.ts +18 -0
- package/src/database/migrations/runner.ts +4 -0
- package/src/database/repositories/content.ts +11 -0
- package/src/database/repositories/media.ts +40 -10
- package/src/database/repositories/taxonomy.ts +193 -89
- package/src/database/types.ts +12 -3
- package/src/emdash-runtime.ts +16 -3
- package/src/fields/file.ts +7 -6
- package/src/fields/image.ts +12 -11
- package/src/fields/types.ts +3 -0
- package/src/i18n/resolve.ts +37 -0
- package/src/index.ts +1 -1
- package/src/loader.ts +49 -2
- package/src/mcp/server.ts +114 -26
- package/src/media/mime.ts +75 -0
- package/src/menus/index.ts +143 -124
- package/src/menus/types.ts +15 -1
- package/src/plugins/types.ts +81 -191
- package/src/request-cache.ts +6 -2
- package/src/request-context.ts +42 -2
- package/src/schema/registry.ts +5 -5
- package/src/schema/types.ts +3 -2
- package/src/schema/zod-generator.ts +12 -2
- package/src/seed/apply.ts +157 -54
- package/src/seed/types.ts +18 -1
- package/src/seed/validate.ts +27 -13
- package/src/taxonomies/index.ts +230 -213
- package/src/taxonomies/types.ts +10 -0
- package/dist/apply-BzltprvY.mjs.map +0 -1
- package/dist/content-8lOYF0pr.mjs.map +0 -1
- package/dist/index-BFRaVcD6.d.mts.map +0 -1
- package/dist/loader-CKLbBnhK.mjs.map +0 -1
- package/dist/media-BW32b4gi.mjs.map +0 -1
- package/dist/registry-Dw70ChxB.mjs.map +0 -1
- package/dist/request-cache-B-bmkipQ.mjs.map +0 -1
- package/dist/runner-C7ADox5q.mjs.map +0 -1
- package/dist/search-dOGEccMa.mjs.map +0 -1
- package/dist/taxonomies-ZlRtD6AG.mjs +0 -315
- package/dist/taxonomies-ZlRtD6AG.mjs.map +0 -1
- package/dist/types-4fVtCIm0.mjs +0 -68
- package/dist/types-4fVtCIm0.mjs.map +0 -1
- package/dist/types-BSyXeCFW.d.mts.map +0 -1
- package/dist/types-BuBIptGk.d.mts.map +0 -1
- package/dist/types-CIOg5AR8.mjs.map +0 -1
- package/dist/types-CrtWgIvl.d.mts.map +0 -1
- package/dist/validate-Baqf0slj.mjs.map +0 -1
- package/dist/validate-BfQh_C_y.d.mts.map +0 -1
- package/dist/version-DoxrVdYf.mjs +0 -7
- package/dist/zod-generator-CC0xNe_K.mjs.map +0 -1
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { n as validateJsonFieldName, r as validatePluginIdentifier, t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
|
|
2
2
|
import { s as jsonExtractExpr } from "./dialect-helpers-BKCvISIQ.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { r as
|
|
5
|
-
import {
|
|
6
|
-
import { t as
|
|
7
|
-
import {
|
|
8
|
-
import { t as OptionsRepository } from "./options-
|
|
9
|
-
import { t as withTransaction } from "./transaction-
|
|
10
|
-
import { t as RedirectRepository } from "./redirect-
|
|
11
|
-
import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-
|
|
12
|
-
import { t as BylineRepository } from "./byline-
|
|
13
|
-
import { r as
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { r as hashString } from "./zod-generator-
|
|
17
|
-
import { i as FTSManager, n as SchemaRegistry } from "./registry-
|
|
18
|
-
import { r as getDb } from "./loader-
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { i as
|
|
22
|
-
import {
|
|
3
|
+
import { r as isI18nEnabled } from "./config-CVssduLe.mjs";
|
|
4
|
+
import { a as slugify, r as RevisionRepository, t as ContentRepository } from "./content-CERxPUN0.mjs";
|
|
5
|
+
import { r as encodeBase64, t as decodeBase64 } from "./base64-MBPo9ozB.mjs";
|
|
6
|
+
import { i as encodeCursor, n as InvalidCursorError, r as decodeCursor, t as EmDashValidationError } from "./types-BIgulNsW.mjs";
|
|
7
|
+
import { t as MediaRepository } from "./media-1fFhub9c.mjs";
|
|
8
|
+
import { t as OptionsRepository } from "./options-nPxWnrya.mjs";
|
|
9
|
+
import { t as withTransaction } from "./transaction-D44LBXvU.mjs";
|
|
10
|
+
import { t as RedirectRepository } from "./redirect-C5H7VGIX.mjs";
|
|
11
|
+
import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-BK1oZS-l.mjs";
|
|
12
|
+
import { t as BylineRepository } from "./byline-gFn1r0vA.mjs";
|
|
13
|
+
import { r as invalidateRedirectCache } from "./cache-BAJbeoZ8.mjs";
|
|
14
|
+
import { t as isMissingTableError } from "./db-errors-B7P2pSCn.mjs";
|
|
15
|
+
import { n as requestCached } from "./request-cache-D4I69LeL.mjs";
|
|
16
|
+
import { r as hashString } from "./zod-generator-CHnJUP2l.mjs";
|
|
17
|
+
import { i as FTSManager, n as SchemaRegistry } from "./registry-Do34mz_P.mjs";
|
|
18
|
+
import { r as getDb } from "./loader-ou_PXAjg.mjs";
|
|
19
|
+
import { a as ssrfSafeFetch, i as resolveAndValidateExternalUrl, o as stripCredentialHeaders, r as SsrfError, s as validateExternalUrl } from "./apply-Ded_1vng.mjs";
|
|
20
|
+
import { d as resolveLocale, f as resolveLocaleChain } from "./taxonomies-Bw76xAxo.mjs";
|
|
21
|
+
import { i as pluginManifestSchema } from "./manifest-schema-CXAbd1vH.mjs";
|
|
22
|
+
import { i as normalizeCapabilities } from "./types-DiI8NOG_.mjs";
|
|
23
|
+
import { t as generatePreviewToken } from "./tokens-CyRDPVW2.mjs";
|
|
23
24
|
import { sql } from "kysely";
|
|
24
25
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
25
26
|
import { ulid } from "ulidx";
|
|
@@ -738,9 +739,6 @@ var PluginStorageRepository = class {
|
|
|
738
739
|
|
|
739
740
|
//#endregion
|
|
740
741
|
//#region src/fields/image.ts
|
|
741
|
-
/**
|
|
742
|
-
* Image field schema
|
|
743
|
-
*/
|
|
744
742
|
const imageSchema = z.object({
|
|
745
743
|
id: z.string(),
|
|
746
744
|
src: z.string(),
|
|
@@ -748,17 +746,39 @@ const imageSchema = z.object({
|
|
|
748
746
|
width: z.number().optional(),
|
|
749
747
|
height: z.number().optional()
|
|
750
748
|
});
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
* References media items from the media library
|
|
754
|
-
*/
|
|
755
|
-
function image(options) {
|
|
749
|
+
function image(options = {}) {
|
|
750
|
+
const validation = options.allowedTypes && options.allowedTypes.length > 0 ? { allowedMimeTypes: [...options.allowedTypes] } : void 0;
|
|
756
751
|
return {
|
|
757
752
|
type: "image",
|
|
758
753
|
columnType: "TEXT",
|
|
759
|
-
schema: options
|
|
754
|
+
schema: options.required === false ? imageSchema.optional() : imageSchema,
|
|
760
755
|
options,
|
|
761
|
-
ui: { widget: "image" }
|
|
756
|
+
ui: { widget: "image" },
|
|
757
|
+
validation
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
//#endregion
|
|
762
|
+
//#region src/fields/file.ts
|
|
763
|
+
function file(options = {}) {
|
|
764
|
+
const fileObjSchema = z.object({
|
|
765
|
+
id: z.string(),
|
|
766
|
+
url: z.string(),
|
|
767
|
+
filename: z.string(),
|
|
768
|
+
mimeType: z.string(),
|
|
769
|
+
size: z.number()
|
|
770
|
+
});
|
|
771
|
+
return {
|
|
772
|
+
type: "file",
|
|
773
|
+
columnType: "TEXT",
|
|
774
|
+
schema: options.required ? fileObjSchema : fileObjSchema.optional(),
|
|
775
|
+
options,
|
|
776
|
+
ui: {
|
|
777
|
+
widget: "file",
|
|
778
|
+
helpText: options.helpText,
|
|
779
|
+
maxSize: options.maxSize
|
|
780
|
+
},
|
|
781
|
+
validation: options.allowedTypes && options.allowedTypes.length > 0 ? { allowedMimeTypes: [...options.allowedTypes] } : void 0
|
|
762
782
|
};
|
|
763
783
|
}
|
|
764
784
|
|
|
@@ -977,6 +997,115 @@ function validateRev(rev, item) {
|
|
|
977
997
|
return { valid: true };
|
|
978
998
|
}
|
|
979
999
|
|
|
1000
|
+
//#endregion
|
|
1001
|
+
//#region src/media/mime.ts
|
|
1002
|
+
function normalizeMime(mime) {
|
|
1003
|
+
return mime.split(";")[0].trim().toLowerCase();
|
|
1004
|
+
}
|
|
1005
|
+
function matchesMimeAllowlist(mime, allowList) {
|
|
1006
|
+
const normalized = normalizeMime(mime);
|
|
1007
|
+
for (const entry of allowList) {
|
|
1008
|
+
if (!entry || !entry.includes("/")) continue;
|
|
1009
|
+
const normalizedEntry = normalizeMime(entry);
|
|
1010
|
+
if (normalizedEntry.endsWith("/")) {
|
|
1011
|
+
if (normalized.startsWith(normalizedEntry)) return true;
|
|
1012
|
+
} else if (normalized === normalizedEntry) return true;
|
|
1013
|
+
}
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Extract the `allowedMimeTypes` list from a `_emdash_fields.validation` row
|
|
1018
|
+
* (raw JSON string). Returns null when the value is missing, malformed, or the
|
|
1019
|
+
* list is empty — callers treat that as "no field-specific constraint".
|
|
1020
|
+
*/
|
|
1021
|
+
function parseAllowedMimeTypes(rawValidation) {
|
|
1022
|
+
if (!rawValidation) return null;
|
|
1023
|
+
try {
|
|
1024
|
+
const parsed = JSON.parse(rawValidation);
|
|
1025
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
1026
|
+
const list = parsed.allowedMimeTypes;
|
|
1027
|
+
if (!Array.isArray(list) || list.length === 0) return null;
|
|
1028
|
+
return list.filter((entry) => typeof entry === "string");
|
|
1029
|
+
} catch {
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
//#endregion
|
|
1035
|
+
//#region src/api/handlers/validate-media-fields.ts
|
|
1036
|
+
function asMediaRef(value) {
|
|
1037
|
+
if (value === null || value === void 0) return null;
|
|
1038
|
+
if (typeof value !== "object" || Array.isArray(value)) return null;
|
|
1039
|
+
return value;
|
|
1040
|
+
}
|
|
1041
|
+
function fail(message) {
|
|
1042
|
+
return {
|
|
1043
|
+
success: false,
|
|
1044
|
+
error: {
|
|
1045
|
+
code: "INVALID_MIME_FOR_FIELD",
|
|
1046
|
+
message
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
async function loadMediaFieldsForCollection(db, collectionSlug) {
|
|
1051
|
+
const rows = await db.selectFrom("_emdash_fields").innerJoin("_emdash_collections", "_emdash_collections.id", "_emdash_fields.collection_id").select([
|
|
1052
|
+
"_emdash_fields.slug",
|
|
1053
|
+
"_emdash_fields.type",
|
|
1054
|
+
"_emdash_fields.validation"
|
|
1055
|
+
]).where("_emdash_collections.slug", "=", collectionSlug).where("_emdash_fields.type", "in", ["file", "image"]).execute();
|
|
1056
|
+
const out = [];
|
|
1057
|
+
for (const row of rows) {
|
|
1058
|
+
const list = parseAllowedMimeTypes(row.validation);
|
|
1059
|
+
if (!list) continue;
|
|
1060
|
+
out.push({
|
|
1061
|
+
slug: row.slug,
|
|
1062
|
+
type: row.type,
|
|
1063
|
+
allowedMimeTypes: list
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
return out;
|
|
1067
|
+
}
|
|
1068
|
+
async function validateMediaFields(db, collectionSlug, data) {
|
|
1069
|
+
const fields = await requestCached(`mediaFields:${collectionSlug}`, () => loadMediaFieldsForCollection(db, collectionSlug));
|
|
1070
|
+
if (fields.length === 0) return {
|
|
1071
|
+
success: true,
|
|
1072
|
+
data: true
|
|
1073
|
+
};
|
|
1074
|
+
const localIds = /* @__PURE__ */ new Set();
|
|
1075
|
+
for (const field of fields) {
|
|
1076
|
+
const ref = asMediaRef(data[field.slug]);
|
|
1077
|
+
if (!ref) continue;
|
|
1078
|
+
if ((typeof ref.provider === "string" ? ref.provider : "local") === "local" && typeof ref.id === "string") localIds.add(ref.id);
|
|
1079
|
+
}
|
|
1080
|
+
const idList = [...localIds];
|
|
1081
|
+
const mimeById = /* @__PURE__ */ new Map();
|
|
1082
|
+
if (idList.length > 0) for (const batch of chunks(idList, SQL_BATCH_SIZE)) {
|
|
1083
|
+
const rows = await db.selectFrom("media").select(["id", "mime_type"]).where("id", "in", batch).execute();
|
|
1084
|
+
for (const r of rows) mimeById.set(r.id, r.mime_type);
|
|
1085
|
+
}
|
|
1086
|
+
for (const field of fields) {
|
|
1087
|
+
const value = data[field.slug];
|
|
1088
|
+
if (value === null || value === void 0) continue;
|
|
1089
|
+
const ref = asMediaRef(value);
|
|
1090
|
+
if (!ref) continue;
|
|
1091
|
+
const provider = typeof ref.provider === "string" ? ref.provider : "local";
|
|
1092
|
+
let mime;
|
|
1093
|
+
if (provider === "local") {
|
|
1094
|
+
if (typeof ref.id !== "string") return fail(`Field '${field.slug}' references media with an invalid id`);
|
|
1095
|
+
mime = mimeById.get(ref.id);
|
|
1096
|
+
if (!mime) return fail(`Field '${field.slug}' references media with unknown MIME type`);
|
|
1097
|
+
} else {
|
|
1098
|
+
if (typeof ref.mimeType !== "string") return fail(`Field '${field.slug}' requires a mimeType declaration for non-local media`);
|
|
1099
|
+
mime = ref.mimeType;
|
|
1100
|
+
}
|
|
1101
|
+
if (!matchesMimeAllowlist(mime, field.allowedMimeTypes)) return fail(`Field '${field.slug}' does not accept ${mime}`);
|
|
1102
|
+
}
|
|
1103
|
+
return {
|
|
1104
|
+
success: true,
|
|
1105
|
+
data: true
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
|
|
980
1109
|
//#endregion
|
|
981
1110
|
//#region src/api/handlers/content.ts
|
|
982
1111
|
/**
|
|
@@ -1254,6 +1383,8 @@ async function handleContentCreate(db, collection, body) {
|
|
|
1254
1383
|
message: `Collection "${collection}" does not have SEO enabled. Remove the seo field or enable SEO on this collection.`
|
|
1255
1384
|
}
|
|
1256
1385
|
};
|
|
1386
|
+
const mimeCheck = await validateMediaFields(db, collection, body.data);
|
|
1387
|
+
if (!mimeCheck.success) return mimeCheck;
|
|
1257
1388
|
const item = await withTransaction(db, async (trx) => {
|
|
1258
1389
|
const repo = new ContentRepository(trx);
|
|
1259
1390
|
const bylineRepo = new BylineRepository(trx);
|
|
@@ -1278,6 +1409,10 @@ async function handleContentCreate(db, collection, body) {
|
|
|
1278
1409
|
created.primaryBylineId = body.bylines[0]?.bylineId ?? null;
|
|
1279
1410
|
}
|
|
1280
1411
|
await hydrateBylines(trx, collection, created);
|
|
1412
|
+
if (body.translationOf) {
|
|
1413
|
+
const { TaxonomyRepository } = await import("./taxonomy-D6NvlKo8.mjs").then((n) => n.n);
|
|
1414
|
+
await new TaxonomyRepository(trx).copyEntryTerms(collection, body.translationOf, created.id);
|
|
1415
|
+
}
|
|
1281
1416
|
if (body.seo && hasSeo) created.seo = await new SeoRepository(trx).upsert(collection, created.id, body.seo);
|
|
1282
1417
|
else if (hasSeo) created.seo = { ...SEO_DEFAULTS };
|
|
1283
1418
|
return created;
|
|
@@ -1348,6 +1483,10 @@ async function handleContentUpdate(db, collection, id, body) {
|
|
|
1348
1483
|
message: `Collection "${collection}" does not have SEO enabled. Remove the seo field or enable SEO on this collection.`
|
|
1349
1484
|
}
|
|
1350
1485
|
};
|
|
1486
|
+
if (body.data) {
|
|
1487
|
+
const mimeCheck = await validateMediaFields(db, collection, body.data);
|
|
1488
|
+
if (!mimeCheck.success) return mimeCheck;
|
|
1489
|
+
}
|
|
1351
1490
|
const resolvedId = await resolveId(new ContentRepository(db), collection, id) ?? id;
|
|
1352
1491
|
const item = await withTransaction(db, async (trx) => {
|
|
1353
1492
|
const trxRepo = new ContentRepository(trx);
|
|
@@ -2659,6 +2798,8 @@ const HTTP_SCHEME_RE = /^https?:\/\//i;
|
|
|
2659
2798
|
const httpUrl = z$1.string().url().refine((url) => HTTP_SCHEME_RE.test(url), "URL must use http or https");
|
|
2660
2799
|
/** BCP 47 locale code — language with optional script/region subtags (e.g. en, en-US, pt-BR, es-419, zh-Hant) */
|
|
2661
2800
|
const localeCode = z$1.string().regex(/^[a-z]{2,3}(-[a-z0-9]{2,8})*$/i, "Invalid locale code").transform((v) => v.toLowerCase());
|
|
2801
|
+
/** Shared `?locale=xx` query shape for endpoints that filter by locale. */
|
|
2802
|
+
const localeFilterQuery = z$1.object({ locale: z$1.string().min(1).optional() }).meta({ id: "LocaleFilterQuery" });
|
|
2662
2803
|
/** Standard API error response */
|
|
2663
2804
|
const apiErrorSchema = z$1.object({ error: z$1.object({
|
|
2664
2805
|
code: z$1.string().meta({
|
|
@@ -2862,7 +3003,14 @@ const contentTranslationsResponseSchema = z$1.object({
|
|
|
2862
3003
|
|
|
2863
3004
|
//#endregion
|
|
2864
3005
|
//#region src/api/schemas/media.ts
|
|
2865
|
-
|
|
3006
|
+
/**
|
|
3007
|
+
* Accepts a comma-separated string (from URL query params) or an array of
|
|
3008
|
+
* strings (from JSON body or programmatic use) and normalises to string[].
|
|
3009
|
+
*/
|
|
3010
|
+
const mimeTypeFilter = z$1.union([z$1.string(), z$1.array(z$1.string())]).transform((v) => {
|
|
3011
|
+
return (Array.isArray(v) ? v : v.split(",")).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
3012
|
+
}).optional();
|
|
3013
|
+
const mediaListQuery = cursorPaginationQuery.extend({ mimeType: mimeTypeFilter }).meta({ id: "MediaListQuery" });
|
|
2866
3014
|
const mediaUpdateBody = z$1.object({
|
|
2867
3015
|
alt: z$1.string().optional(),
|
|
2868
3016
|
caption: z$1.string().optional(),
|
|
@@ -2878,7 +3026,7 @@ const mediaConfirmBody = z$1.object({
|
|
|
2878
3026
|
}).meta({ id: "MediaConfirmBody" });
|
|
2879
3027
|
const mediaProviderListQuery = cursorPaginationQuery.extend({
|
|
2880
3028
|
query: z$1.string().optional(),
|
|
2881
|
-
mimeType:
|
|
3029
|
+
mimeType: mimeTypeFilter
|
|
2882
3030
|
}).meta({ id: "MediaProviderListQuery" });
|
|
2883
3031
|
const mediaStatusSchema = z$1.enum([
|
|
2884
3032
|
"pending",
|
|
@@ -2976,7 +3124,8 @@ const fieldValidation = z$1.object({
|
|
|
2976
3124
|
options: z$1.array(z$1.string()).optional(),
|
|
2977
3125
|
subFields: z$1.array(repeaterSubFieldSchema).min(1).optional(),
|
|
2978
3126
|
minItems: z$1.number().int().min(0).optional(),
|
|
2979
|
-
maxItems: z$1.number().int().min(1).optional()
|
|
3127
|
+
maxItems: z$1.number().int().min(1).optional(),
|
|
3128
|
+
allowedMimeTypes: z$1.array(z$1.string().regex(/^[a-z0-9][a-z0-9!#$&^_+\-.]*\/[a-z0-9!#$&^_+\-.]*$/i, "Invalid MIME type")).min(1, "allowedMimeTypes must not be empty — omit the field to allow all types").max(64, "allowedMimeTypes may contain at most 64 entries").optional()
|
|
2980
3129
|
}).optional();
|
|
2981
3130
|
const fieldWidgetOptions = z$1.record(z$1.string(), z$1.unknown()).optional();
|
|
2982
3131
|
const createCollectionBody = z$1.object({
|
|
@@ -3014,7 +3163,7 @@ const createFieldBody = z$1.object({
|
|
|
3014
3163
|
required: z$1.boolean().optional(),
|
|
3015
3164
|
unique: z$1.boolean().optional(),
|
|
3016
3165
|
defaultValue: z$1.unknown().optional(),
|
|
3017
|
-
validation: fieldValidation,
|
|
3166
|
+
validation: fieldValidation.nullable(),
|
|
3018
3167
|
widget: z$1.string().optional(),
|
|
3019
3168
|
options: fieldWidgetOptions,
|
|
3020
3169
|
sortOrder: z$1.number().int().min(0).optional(),
|
|
@@ -3026,7 +3175,7 @@ const updateFieldBody = z$1.object({
|
|
|
3026
3175
|
required: z$1.boolean().optional(),
|
|
3027
3176
|
unique: z$1.boolean().optional(),
|
|
3028
3177
|
defaultValue: z$1.unknown().optional(),
|
|
3029
|
-
validation: fieldValidation,
|
|
3178
|
+
validation: fieldValidation.nullable(),
|
|
3030
3179
|
widget: z$1.string().optional(),
|
|
3031
3180
|
options: fieldWidgetOptions,
|
|
3032
3181
|
sortOrder: z$1.number().int().min(0).optional(),
|
|
@@ -3292,7 +3441,9 @@ const menuItemType = z$1.string().min(1);
|
|
|
3292
3441
|
const safeHref = z$1.string().trim().refine(isSafeHref, "URL must use http, https, mailto, tel, a relative path, or a fragment identifier");
|
|
3293
3442
|
const createMenuBody = z$1.object({
|
|
3294
3443
|
name: z$1.string().min(1),
|
|
3295
|
-
label: z$1.string().min(1)
|
|
3444
|
+
label: z$1.string().min(1),
|
|
3445
|
+
locale: z$1.string().min(1).optional(),
|
|
3446
|
+
translationOf: z$1.string().min(1).optional()
|
|
3296
3447
|
}).meta({ id: "CreateMenuBody" });
|
|
3297
3448
|
const updateMenuBody = z$1.object({ label: z$1.string().min(1).optional() }).meta({ id: "UpdateMenuBody" });
|
|
3298
3449
|
const createMenuItemBody = z$1.object({
|
|
@@ -3328,7 +3479,9 @@ const menuSchema = z$1.object({
|
|
|
3328
3479
|
name: z$1.string(),
|
|
3329
3480
|
label: z$1.string(),
|
|
3330
3481
|
created_at: z$1.string(),
|
|
3331
|
-
updated_at: z$1.string()
|
|
3482
|
+
updated_at: z$1.string(),
|
|
3483
|
+
locale: z$1.string(),
|
|
3484
|
+
translation_group: z$1.string().nullable()
|
|
3332
3485
|
}).meta({ id: "Menu" });
|
|
3333
3486
|
const menuItemSchema = z$1.object({
|
|
3334
3487
|
id: z$1.string(),
|
|
@@ -3343,8 +3496,20 @@ const menuItemSchema = z$1.object({
|
|
|
3343
3496
|
title_attr: z$1.string().nullable(),
|
|
3344
3497
|
target: z$1.string().nullable(),
|
|
3345
3498
|
css_classes: z$1.string().nullable(),
|
|
3346
|
-
created_at: z$1.string()
|
|
3499
|
+
created_at: z$1.string(),
|
|
3500
|
+
locale: z$1.string(),
|
|
3501
|
+
translation_group: z$1.string().nullable()
|
|
3347
3502
|
}).meta({ id: "MenuItem" });
|
|
3503
|
+
const menuTranslationsSchema = z$1.object({
|
|
3504
|
+
translationGroup: z$1.string().nullable(),
|
|
3505
|
+
translations: z$1.array(z$1.object({
|
|
3506
|
+
id: z$1.string(),
|
|
3507
|
+
name: z$1.string(),
|
|
3508
|
+
label: z$1.string(),
|
|
3509
|
+
locale: z$1.string(),
|
|
3510
|
+
updatedAt: z$1.string()
|
|
3511
|
+
}))
|
|
3512
|
+
}).meta({ id: "MenuTranslations" });
|
|
3348
3513
|
const menuListItemSchema = menuSchema.extend({ itemCount: z$1.number().int() }).meta({ id: "MenuListItem" });
|
|
3349
3514
|
const menuWithItemsSchema = menuSchema.extend({ items: z$1.array(menuItemSchema) }).meta({ id: "MenuWithItems" });
|
|
3350
3515
|
|
|
@@ -3355,14 +3520,19 @@ const collectionSlugPattern = /^[a-z][a-z0-9_]*$/;
|
|
|
3355
3520
|
const createTaxonomyDefBody = z$1.object({
|
|
3356
3521
|
name: z$1.string().min(1).max(63).regex(/^[a-z][a-z0-9_]*$/, "Name must be lowercase alphanumeric with underscores"),
|
|
3357
3522
|
label: z$1.string().min(1).max(200),
|
|
3523
|
+
labelSingular: z$1.string().min(1).max(200).optional(),
|
|
3358
3524
|
hierarchical: z$1.boolean().optional().default(false),
|
|
3359
|
-
collections: z$1.array(z$1.string().min(1).max(63).regex(collectionSlugPattern, "Invalid collection slug format")).max(100).optional().default([])
|
|
3525
|
+
collections: z$1.array(z$1.string().min(1).max(63).regex(collectionSlugPattern, "Invalid collection slug format")).max(100).optional().default([]),
|
|
3526
|
+
locale: z$1.string().min(1).optional(),
|
|
3527
|
+
translationOf: z$1.string().min(1).optional()
|
|
3360
3528
|
}).meta({ id: "CreateTaxonomyDefBody" });
|
|
3361
3529
|
const createTermBody = z$1.object({
|
|
3362
3530
|
slug: z$1.string().min(1),
|
|
3363
3531
|
label: z$1.string().min(1),
|
|
3364
3532
|
parentId: z$1.string().nullish(),
|
|
3365
|
-
description: z$1.string().optional()
|
|
3533
|
+
description: z$1.string().optional(),
|
|
3534
|
+
locale: z$1.string().min(1).optional(),
|
|
3535
|
+
translationOf: z$1.string().min(1).optional()
|
|
3366
3536
|
}).meta({ id: "CreateTermBody" });
|
|
3367
3537
|
const updateTermBody = z$1.object({
|
|
3368
3538
|
slug: z$1.string().min(1).optional(),
|
|
@@ -3376,8 +3546,19 @@ const taxonomyDefSchema = z$1.object({
|
|
|
3376
3546
|
label: z$1.string(),
|
|
3377
3547
|
labelSingular: z$1.string().optional(),
|
|
3378
3548
|
hierarchical: z$1.boolean(),
|
|
3379
|
-
collections: z$1.array(z$1.string())
|
|
3549
|
+
collections: z$1.array(z$1.string()),
|
|
3550
|
+
locale: z$1.string(),
|
|
3551
|
+
translationGroup: z$1.string().nullable()
|
|
3380
3552
|
}).meta({ id: "TaxonomyDef" });
|
|
3553
|
+
const taxonomyDefTranslationsSchema = z$1.object({
|
|
3554
|
+
translationGroup: z$1.string().nullable(),
|
|
3555
|
+
translations: z$1.array(z$1.object({
|
|
3556
|
+
id: z$1.string(),
|
|
3557
|
+
name: z$1.string(),
|
|
3558
|
+
label: z$1.string(),
|
|
3559
|
+
locale: z$1.string()
|
|
3560
|
+
}))
|
|
3561
|
+
}).meta({ id: "TaxonomyDefTranslations" });
|
|
3381
3562
|
const taxonomyListResponseSchema = z$1.object({ taxonomies: z$1.array(taxonomyDefSchema) }).meta({ id: "TaxonomyListResponse" });
|
|
3382
3563
|
const termSchema = z$1.object({
|
|
3383
3564
|
id: z$1.string(),
|
|
@@ -3385,8 +3566,19 @@ const termSchema = z$1.object({
|
|
|
3385
3566
|
slug: z$1.string(),
|
|
3386
3567
|
label: z$1.string(),
|
|
3387
3568
|
parentId: z$1.string().nullable(),
|
|
3388
|
-
description: z$1.string().optional()
|
|
3569
|
+
description: z$1.string().optional(),
|
|
3570
|
+
locale: z$1.string(),
|
|
3571
|
+
translationGroup: z$1.string().nullable()
|
|
3389
3572
|
}).meta({ id: "Term" });
|
|
3573
|
+
const termTranslationsSchema = z$1.object({
|
|
3574
|
+
translationGroup: z$1.string().nullable(),
|
|
3575
|
+
translations: z$1.array(z$1.object({
|
|
3576
|
+
id: z$1.string(),
|
|
3577
|
+
slug: z$1.string(),
|
|
3578
|
+
label: z$1.string(),
|
|
3579
|
+
locale: z$1.string()
|
|
3580
|
+
}))
|
|
3581
|
+
}).meta({ id: "TermTranslations" });
|
|
3390
3582
|
const termWithCountSchema = z$1.object({
|
|
3391
3583
|
id: z$1.string(),
|
|
3392
3584
|
name: z$1.string(),
|
|
@@ -3395,7 +3587,9 @@ const termWithCountSchema = z$1.object({
|
|
|
3395
3587
|
parentId: z$1.string().nullable(),
|
|
3396
3588
|
description: z$1.string().optional(),
|
|
3397
3589
|
count: z$1.number().int(),
|
|
3398
|
-
children: z$1.array(z$1.lazy(() => termWithCountSchema))
|
|
3590
|
+
children: z$1.array(z$1.lazy(() => termWithCountSchema)),
|
|
3591
|
+
locale: z$1.string(),
|
|
3592
|
+
translationGroup: z$1.string().nullable()
|
|
3399
3593
|
}).meta({ id: "TermWithCount" });
|
|
3400
3594
|
const termListResponseSchema = z$1.object({ terms: z$1.array(termWithCountSchema) }).meta({ id: "TermListResponse" });
|
|
3401
3595
|
const termResponseSchema = z$1.object({ term: termSchema }).meta({ id: "TermResponse" });
|
|
@@ -8926,53 +9120,53 @@ async function getCommentCountWithDb(db, collection, contentId) {
|
|
|
8926
9120
|
//#endregion
|
|
8927
9121
|
//#region src/menus/index.ts
|
|
8928
9122
|
/**
|
|
8929
|
-
* Get menu by name with resolved URLs
|
|
9123
|
+
* Get a menu by name with resolved URLs.
|
|
8930
9124
|
*
|
|
8931
9125
|
* @example
|
|
8932
9126
|
* ```ts
|
|
8933
|
-
* import { getMenu } from "emdash";
|
|
8934
|
-
*
|
|
8935
9127
|
* const menu = await getMenu("primary");
|
|
8936
|
-
*
|
|
8937
|
-
* console.log(menu.items); // Array of MenuItem with resolved URLs
|
|
8938
|
-
* }
|
|
9128
|
+
* const menuEs = await getMenu("primary", { locale: "es" });
|
|
8939
9129
|
* ```
|
|
8940
9130
|
*/
|
|
8941
|
-
function getMenu(name) {
|
|
8942
|
-
|
|
8943
|
-
|
|
9131
|
+
function getMenu(name, options = {}) {
|
|
9132
|
+
const locale = resolveLocale(options.locale);
|
|
9133
|
+
return requestCached(`menu:${name}:${locale ?? "*"}`, async () => {
|
|
9134
|
+
return getMenuWithDb(name, await getDb(), { locale });
|
|
8944
9135
|
});
|
|
8945
9136
|
}
|
|
8946
9137
|
/**
|
|
8947
|
-
* Get menu by name with resolved URLs (with explicit db)
|
|
8948
|
-
*
|
|
8949
|
-
* @internal Use `getMenu()` in templates. This variant is for admin routes
|
|
8950
|
-
* that already have a database handle.
|
|
9138
|
+
* Get menu by name with resolved URLs (with explicit db). Internal helper for
|
|
9139
|
+
* admin routes that already have a database handle.
|
|
8951
9140
|
*/
|
|
8952
|
-
async function getMenuWithDb(name, db) {
|
|
8953
|
-
const
|
|
9141
|
+
async function getMenuWithDb(name, db, options = {}) {
|
|
9142
|
+
const chain = resolveLocaleChain(options.locale);
|
|
9143
|
+
const selectMenu = () => db.selectFrom("_emdash_menus").selectAll().where("name", "=", name);
|
|
9144
|
+
let menuRow;
|
|
9145
|
+
if (chain.length === 0) menuRow = await selectMenu().orderBy("locale", "asc").executeTakeFirst();
|
|
9146
|
+
else {
|
|
9147
|
+
menuRow = void 0;
|
|
9148
|
+
for (const locale of chain) {
|
|
9149
|
+
menuRow = await selectMenu().where("locale", "=", locale).executeTakeFirst();
|
|
9150
|
+
if (menuRow) break;
|
|
9151
|
+
}
|
|
9152
|
+
}
|
|
8954
9153
|
if (!menuRow) return null;
|
|
8955
|
-
const items = await buildMenuTree(await db.selectFrom("_emdash_menu_items").selectAll().$castTo().where("menu_id", "=", menuRow.id).orderBy("sort_order", "asc").execute(), db);
|
|
9154
|
+
const items = await buildMenuTree(await db.selectFrom("_emdash_menu_items").selectAll().$castTo().where("menu_id", "=", menuRow.id).orderBy("sort_order", "asc").execute(), db, menuRow.locale);
|
|
8956
9155
|
return {
|
|
8957
9156
|
id: menuRow.id,
|
|
8958
9157
|
name: menuRow.name,
|
|
8959
9158
|
label: menuRow.label,
|
|
8960
|
-
items
|
|
9159
|
+
items,
|
|
9160
|
+
locale: menuRow.locale,
|
|
9161
|
+
translationGroup: menuRow.translation_group
|
|
8961
9162
|
};
|
|
8962
9163
|
}
|
|
8963
9164
|
/**
|
|
8964
|
-
* Get all menus (without items - for admin list
|
|
8965
|
-
*
|
|
8966
|
-
* @example
|
|
8967
|
-
* ```ts
|
|
8968
|
-
* import { getMenus } from "emdash";
|
|
8969
|
-
*
|
|
8970
|
-
* const menus = await getMenus();
|
|
8971
|
-
* console.log(menus); // [{ id, name, label }]
|
|
8972
|
-
* ```
|
|
9165
|
+
* Get all menus (without items, locale-filtered — for admin list / site nav
|
|
9166
|
+
* summaries). When no locale is configured, returns menus across all locales.
|
|
8973
9167
|
*/
|
|
8974
|
-
async function getMenus() {
|
|
8975
|
-
return getMenusWithDb(await getDb());
|
|
9168
|
+
async function getMenus(options = {}) {
|
|
9169
|
+
return getMenusWithDb(await getDb(), options);
|
|
8976
9170
|
}
|
|
8977
9171
|
/**
|
|
8978
9172
|
* Get all menus (with explicit db)
|
|
@@ -8980,17 +9174,23 @@ async function getMenus() {
|
|
|
8980
9174
|
* @internal Use `getMenus()` in templates. This variant is for admin routes
|
|
8981
9175
|
* that already have a database handle.
|
|
8982
9176
|
*/
|
|
8983
|
-
async function getMenusWithDb(db) {
|
|
8984
|
-
|
|
9177
|
+
async function getMenusWithDb(db, options = {}) {
|
|
9178
|
+
const locale = resolveLocale(options.locale);
|
|
9179
|
+
let query = db.selectFrom("_emdash_menus").select([
|
|
8985
9180
|
"id",
|
|
8986
9181
|
"name",
|
|
8987
|
-
"label"
|
|
8988
|
-
|
|
9182
|
+
"label",
|
|
9183
|
+
"locale"
|
|
9184
|
+
]).orderBy("name", "asc");
|
|
9185
|
+
if (locale !== void 0) query = query.where("locale", "=", locale);
|
|
9186
|
+
return query.execute();
|
|
8989
9187
|
}
|
|
8990
9188
|
/**
|
|
8991
|
-
* Build hierarchical menu tree from flat
|
|
9189
|
+
* Build a hierarchical menu tree from a flat list of items. Items are
|
|
9190
|
+
* resolved against the given `locale` so references land on the right
|
|
9191
|
+
* per-locale content rows.
|
|
8992
9192
|
*/
|
|
8993
|
-
async function buildMenuTree(items, db) {
|
|
9193
|
+
async function buildMenuTree(items, db, locale) {
|
|
8994
9194
|
const collectionSlugs = /* @__PURE__ */ new Set();
|
|
8995
9195
|
for (const item of items) {
|
|
8996
9196
|
if (item.reference_collection) collectionSlugs.add(item.reference_collection);
|
|
@@ -9001,7 +9201,7 @@ async function buildMenuTree(items, db) {
|
|
|
9001
9201
|
const rows = await db.selectFrom("_emdash_collections").select(["slug", "url_pattern"]).where("slug", "in", [...collectionSlugs]).execute();
|
|
9002
9202
|
for (const row of rows) urlPatterns.set(row.slug, row.url_pattern);
|
|
9003
9203
|
}
|
|
9004
|
-
const validItems = (await Promise.all(items.map((item) => resolveMenuItem(item, db, urlPatterns)))).filter((item) => item !== null);
|
|
9204
|
+
const validItems = (await Promise.all(items.map((item) => resolveMenuItem(item, db, urlPatterns, locale)))).filter((item) => item !== null);
|
|
9005
9205
|
const itemMap = /* @__PURE__ */ new Map();
|
|
9006
9206
|
const rootItems = [];
|
|
9007
9207
|
for (const item of validItems) itemMap.set(item.id, {
|
|
@@ -9020,11 +9220,11 @@ async function buildMenuTree(items, db) {
|
|
|
9020
9220
|
return rootItems;
|
|
9021
9221
|
}
|
|
9022
9222
|
/**
|
|
9023
|
-
* Resolve a single menu item's URL
|
|
9024
|
-
*
|
|
9025
|
-
*
|
|
9223
|
+
* Resolve a single menu item's URL. `reference_id` is a translation_group
|
|
9224
|
+
* (migration 036 remapped all existing references); we join it against
|
|
9225
|
+
* the per-locale ec_* row or per-locale taxonomy row.
|
|
9026
9226
|
*/
|
|
9027
|
-
async function resolveMenuItem(item, db, urlPatterns) {
|
|
9227
|
+
async function resolveMenuItem(item, db, urlPatterns, locale) {
|
|
9028
9228
|
let url;
|
|
9029
9229
|
try {
|
|
9030
9230
|
switch (item.type) {
|
|
@@ -9033,18 +9233,18 @@ async function resolveMenuItem(item, db, urlPatterns) {
|
|
|
9033
9233
|
break;
|
|
9034
9234
|
case "page":
|
|
9035
9235
|
case "post":
|
|
9036
|
-
url = await resolveContentUrl(item.reference_collection || `${item.type}s`, item.reference_id, db, urlPatterns);
|
|
9236
|
+
url = await resolveContentUrl(item.reference_collection || `${item.type}s`, item.reference_id, db, urlPatterns, locale);
|
|
9037
9237
|
if (url === null) return null;
|
|
9038
9238
|
break;
|
|
9039
9239
|
case "taxonomy":
|
|
9040
|
-
url = await resolveTaxonomyUrl(item.reference_id, db);
|
|
9240
|
+
url = await resolveTaxonomyUrl(item.reference_id, db, locale);
|
|
9041
9241
|
if (url === null) return null;
|
|
9042
9242
|
break;
|
|
9043
9243
|
case "collection":
|
|
9044
9244
|
url = `/${item.reference_collection}/`;
|
|
9045
9245
|
break;
|
|
9046
9246
|
default: if (item.reference_collection && item.reference_id) {
|
|
9047
|
-
url = await resolveContentUrl(item.reference_collection, item.reference_id, db, urlPatterns);
|
|
9247
|
+
url = await resolveContentUrl(item.reference_collection, item.reference_id, db, urlPatterns, locale);
|
|
9048
9248
|
if (url === null) return null;
|
|
9049
9249
|
} else url = "#";
|
|
9050
9250
|
}
|
|
@@ -9073,37 +9273,51 @@ function interpolateUrlPattern(pattern, slug, id) {
|
|
|
9073
9273
|
return pattern.replace(SLUG_PLACEHOLDER, slug).replace(ID_PLACEHOLDER, id);
|
|
9074
9274
|
}
|
|
9075
9275
|
/**
|
|
9076
|
-
* Resolve URL for a content
|
|
9077
|
-
*
|
|
9078
|
-
*
|
|
9079
|
-
*
|
|
9276
|
+
* Resolve the URL for a content reference. `referenceGroup` is the content
|
|
9277
|
+
* row's translation_group; we look up the row in the requested locale
|
|
9278
|
+
* (falling back to the source if no translation exists so the menu link is
|
|
9279
|
+
* still clickable).
|
|
9080
9280
|
*/
|
|
9081
|
-
async function resolveContentUrl(collection,
|
|
9082
|
-
if (!
|
|
9281
|
+
async function resolveContentUrl(collection, referenceGroup, db, urlPatterns, locale) {
|
|
9282
|
+
if (!referenceGroup) return null;
|
|
9083
9283
|
try {
|
|
9084
9284
|
validateIdentifier(collection, "menu item collection");
|
|
9085
|
-
|
|
9086
|
-
SELECT slug FROM ${sql.ref(`ec_${collection}`)}
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9285
|
+
let result = await sql`
|
|
9286
|
+
SELECT id, slug FROM ${sql.ref(`ec_${collection}`)}
|
|
9287
|
+
WHERE translation_group = ${referenceGroup} AND locale = ${locale}
|
|
9288
|
+
LIMIT 1
|
|
9289
|
+
`.execute(db);
|
|
9290
|
+
let row = result.rows[0];
|
|
9291
|
+
if (!row) {
|
|
9292
|
+
result = await sql`
|
|
9293
|
+
SELECT id, slug FROM ${sql.ref(`ec_${collection}`)}
|
|
9294
|
+
WHERE translation_group = ${referenceGroup}
|
|
9295
|
+
ORDER BY locale ASC LIMIT 1
|
|
9296
|
+
`.execute(db);
|
|
9297
|
+
row = result.rows[0];
|
|
9092
9298
|
}
|
|
9093
|
-
|
|
9299
|
+
if (!row) row = (await sql`
|
|
9300
|
+
SELECT id, slug FROM ${sql.ref(`ec_${collection}`)}
|
|
9301
|
+
WHERE id = ${referenceGroup} LIMIT 1
|
|
9302
|
+
`.execute(db)).rows[0];
|
|
9303
|
+
if (!row) return null;
|
|
9304
|
+
const pattern = urlPatterns.get(collection);
|
|
9305
|
+
if (pattern) return interpolateUrlPattern(pattern, row.slug, row.id);
|
|
9306
|
+
return `/${collection}/${row.slug}`;
|
|
9094
9307
|
} catch (error) {
|
|
9095
|
-
console.error(`Failed to resolve content URL for ${collection}/${
|
|
9308
|
+
console.error(`Failed to resolve content URL for ${collection}/${referenceGroup}:`, error);
|
|
9096
9309
|
return null;
|
|
9097
9310
|
}
|
|
9098
9311
|
}
|
|
9099
9312
|
/**
|
|
9100
|
-
* Resolve URL for a taxonomy term
|
|
9101
|
-
*
|
|
9102
|
-
* Returns null if taxonomy not found (item should be skipped)
|
|
9313
|
+
* Resolve URL for a taxonomy term reference. `referenceGroup` is the term's
|
|
9314
|
+
* translation_group; we pick the row in the active locale (or fall back).
|
|
9103
9315
|
*/
|
|
9104
|
-
async function resolveTaxonomyUrl(
|
|
9105
|
-
if (!
|
|
9106
|
-
|
|
9316
|
+
async function resolveTaxonomyUrl(referenceGroup, db, locale) {
|
|
9317
|
+
if (!referenceGroup) return null;
|
|
9318
|
+
let taxonomy = await db.selectFrom("taxonomies").select(["name", "slug"]).where("translation_group", "=", referenceGroup).where("locale", "=", locale).executeTakeFirst();
|
|
9319
|
+
if (!taxonomy) taxonomy = await db.selectFrom("taxonomies").select(["name", "slug"]).where("translation_group", "=", referenceGroup).orderBy("locale", "asc").executeTakeFirst();
|
|
9320
|
+
if (!taxonomy) taxonomy = await db.selectFrom("taxonomies").select(["name", "slug"]).where("id", "=", referenceGroup).executeTakeFirst();
|
|
9107
9321
|
if (!taxonomy) return null;
|
|
9108
9322
|
return `/${taxonomy.name}/${taxonomy.slug}`;
|
|
9109
9323
|
}
|
|
@@ -9679,5 +9893,5 @@ function extractSearchableFields(entry, fields) {
|
|
|
9679
9893
|
}
|
|
9680
9894
|
|
|
9681
9895
|
//#endregion
|
|
9682
|
-
export { isSafeHref as $, NoopSandboxRunner as A, handleContentTranslations as At, HookPipeline as B, getAllSources as C, handleContentGetIncludingTrashed as Ct, probeUrl as D, handleContentPublish as Dt, getUrlSources as E, handleContentPermanentDelete as Et, PluginRouteError as F, portableText as Ft, sanitizeHeadersForSandbox as G, resolveExclusiveHooks as H, PluginRouteRegistry as I, reference as It, parseWxr as J, getTrustedProxyHeaders as K, DEV_CONSOLE_EMAIL_PLUGIN_ID as L,
|
|
9683
|
-
//# sourceMappingURL=search-
|
|
9896
|
+
export { isSafeHref as $, NoopSandboxRunner as A, handleContentTranslations as At, HookPipeline as B, getAllSources as C, handleContentGetIncludingTrashed as Ct, probeUrl as D, handleContentPublish as Dt, getUrlSources as E, handleContentPermanentDelete as Et, PluginRouteError as F, portableText as Ft, sanitizeHeadersForSandbox as G, resolveExclusiveHooks as H, PluginRouteRegistry as I, reference as It, parseWxr as J, getTrustedProxyHeaders as K, DEV_CONSOLE_EMAIL_PLUGIN_ID as L, file as Lt, createNoopSandboxRunner as M, handleContentUnschedule as Mt, PluginManager as N, handleContentUpdate as Nt, registerSource as O, handleContentRestore as Ot, createPluginManager as P, validateRev as Pt, prosemirrorToPortableText as Q, devConsoleEmailDeliver as R, image as Rt, clearSources as S, handleContentGet as St, getSource as T, handleContentListTrashed as Tt, CronExecutor as U, createHookPipeline as V, extractRequestMeta as W, after as X, parseWxrString as Y, portableTextToProsemirror as Z, buildPreviewUrl as _, handleContentCountTrashed as _t, search as a, getCollectionInfo as at, parseWxrDate as b, handleContentDiscardDraft as bt, getWidgetArea as c, handleMediaGet as ct, getMenu as d, handleRevisionGet as dt, sanitizeHref as et, getMenus as f, handleRevisionList as ft, isPreviewRequest as g, handleContentCountScheduled as gt, getPreviewToken as h, handleContentCompare as ht, getSuggestions as i, PluginStateRepository as it, SandboxNotAvailableError as j, handleContentUnpublish as jt, importReusableBlocksAsSections as k, handleContentSchedule as kt, getWidgetAreas as l, handleMediaList as lt, getComments as m, generateManifest as mt, extractSearchableFields as n, getSection as nt, searchCollection as o, handleMediaCreate as ot, getCommentCount as p, handleRevisionRestore as pt, definePlugin as q, getSearchStats as r, getSections as rt, searchWithDb as s, handleMediaDelete as st, extractPlainText as t, loadBundleFromR2 as tt, getWidgetComponents as u, handleMediaUpdate as ut, getPreviewUrl as v, handleContentCreate as vt, getFileSources as w, handleContentList as wt, wxrSource as x, handleContentDuplicate as xt, wordpressRestSource as y, handleContentDelete as yt, EmailPipeline as z };
|
|
9897
|
+
//# sourceMappingURL=search-DuWhx4NG.mjs.map
|