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
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
SeedWidget,
|
|
33
33
|
SeedContentEntry,
|
|
34
34
|
} from "../../seed/types.js";
|
|
35
|
+
import { slugify } from "../../utils/slugify.js";
|
|
35
36
|
|
|
36
37
|
const SETTINGS_PREFIX = "site:";
|
|
37
38
|
|
|
@@ -101,7 +102,7 @@ export const exportSeedCommand = defineCommand({
|
|
|
101
102
|
/**
|
|
102
103
|
* Export database to seed file format
|
|
103
104
|
*/
|
|
104
|
-
async function exportSeed(db: Kysely<Database>, withContent?: string): Promise<SeedFile> {
|
|
105
|
+
export async function exportSeed(db: Kysely<Database>, withContent?: string): Promise<SeedFile> {
|
|
105
106
|
const seed: SeedFile = {
|
|
106
107
|
$schema: "https://emdashcms.com/seed.schema.json",
|
|
107
108
|
version: "1",
|
|
@@ -212,41 +213,69 @@ async function exportCollections(db: Kysely<Database>): Promise<SeedCollection[]
|
|
|
212
213
|
* Export taxonomy definitions and terms
|
|
213
214
|
*/
|
|
214
215
|
async function exportTaxonomies(db: Kysely<Database>): Promise<SeedTaxonomy[]> {
|
|
215
|
-
|
|
216
|
-
|
|
216
|
+
const i18nEnabled = isI18nEnabled();
|
|
217
|
+
|
|
218
|
+
// Mirrors the content export pattern: one entry per (name, locale), stable
|
|
219
|
+
// seed-local id, translations linked via `translationOf` to the anchor's id.
|
|
220
|
+
const defs = await db
|
|
221
|
+
.selectFrom("_emdash_taxonomy_defs")
|
|
222
|
+
.selectAll()
|
|
223
|
+
.orderBy(["name", "locale"])
|
|
224
|
+
.execute();
|
|
217
225
|
|
|
218
226
|
const result: SeedTaxonomy[] = [];
|
|
219
227
|
const termRepo = new TaxonomyRepository(db);
|
|
220
228
|
|
|
229
|
+
// translation_group -> seed-local id of first def we emitted in that group.
|
|
230
|
+
const defGroupToSeedId = new Map<string, string>();
|
|
231
|
+
|
|
221
232
|
for (const def of defs) {
|
|
222
|
-
|
|
223
|
-
|
|
233
|
+
const defSeedId =
|
|
234
|
+
i18nEnabled && def.locale ? `tax:${def.name}:${def.locale}` : `tax:${def.name}`;
|
|
224
235
|
|
|
225
|
-
//
|
|
226
|
-
const
|
|
236
|
+
// Terms in this def's locale.
|
|
237
|
+
const terms = await termRepo.findByName(def.name, { locale: def.locale });
|
|
227
238
|
|
|
228
|
-
//
|
|
239
|
+
// id -> slug for parent resolution within this locale.
|
|
229
240
|
const idToSlug = new Map<string, string>();
|
|
230
|
-
for (const term of terms)
|
|
231
|
-
|
|
232
|
-
|
|
241
|
+
for (const term of terms) idToSlug.set(term.id, term.slug);
|
|
242
|
+
|
|
243
|
+
// translation_group -> seed id of the anchor term.
|
|
244
|
+
const termGroupToSeedId = new Map<string, string>();
|
|
233
245
|
|
|
246
|
+
const seedTerms: SeedTaxonomyTerm[] = [];
|
|
234
247
|
for (const term of terms) {
|
|
248
|
+
const termSeedId =
|
|
249
|
+
i18nEnabled && term.locale
|
|
250
|
+
? `term:${def.name}:${term.slug}:${term.locale}`
|
|
251
|
+
: `term:${def.name}:${term.slug}`;
|
|
252
|
+
|
|
235
253
|
const seedTerm: SeedTaxonomyTerm = {
|
|
254
|
+
id: termSeedId,
|
|
236
255
|
slug: term.slug,
|
|
237
256
|
label: term.label,
|
|
238
257
|
description: typeof term.data?.description === "string" ? term.data.description : undefined,
|
|
239
258
|
};
|
|
240
259
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
260
|
+
if (term.parentId) seedTerm.parent = idToSlug.get(term.parentId);
|
|
261
|
+
|
|
262
|
+
if (i18nEnabled && term.locale) {
|
|
263
|
+
seedTerm.locale = term.locale;
|
|
264
|
+
if (term.translationGroup) {
|
|
265
|
+
const anchor = termGroupToSeedId.get(term.translationGroup);
|
|
266
|
+
if (anchor) seedTerm.translationOf = anchor;
|
|
267
|
+
else termGroupToSeedId.set(term.translationGroup, termSeedId);
|
|
268
|
+
}
|
|
244
269
|
}
|
|
245
270
|
|
|
246
271
|
seedTerms.push(seedTerm);
|
|
247
272
|
}
|
|
248
273
|
|
|
274
|
+
// Anchors first so import can resolve `translationOf`.
|
|
275
|
+
seedTerms.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
|
|
276
|
+
|
|
249
277
|
const taxonomy: SeedTaxonomy = {
|
|
278
|
+
id: defSeedId,
|
|
250
279
|
name: def.name,
|
|
251
280
|
label: def.label,
|
|
252
281
|
labelSingular: def.label_singular || undefined,
|
|
@@ -254,13 +283,23 @@ async function exportTaxonomies(db: Kysely<Database>): Promise<SeedTaxonomy[]> {
|
|
|
254
283
|
collections: def.collections ? JSON.parse(def.collections) : [],
|
|
255
284
|
};
|
|
256
285
|
|
|
257
|
-
if (
|
|
258
|
-
taxonomy.
|
|
286
|
+
if (i18nEnabled && def.locale) {
|
|
287
|
+
taxonomy.locale = def.locale;
|
|
288
|
+
if (def.translation_group) {
|
|
289
|
+
const anchor = defGroupToSeedId.get(def.translation_group);
|
|
290
|
+
if (anchor) taxonomy.translationOf = anchor;
|
|
291
|
+
else defGroupToSeedId.set(def.translation_group, defSeedId);
|
|
292
|
+
}
|
|
259
293
|
}
|
|
260
294
|
|
|
295
|
+
if (seedTerms.length > 0) taxonomy.terms = seedTerms;
|
|
296
|
+
|
|
261
297
|
result.push(taxonomy);
|
|
262
298
|
}
|
|
263
299
|
|
|
300
|
+
// Anchors first at def level too.
|
|
301
|
+
result.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
|
|
302
|
+
|
|
264
303
|
return result;
|
|
265
304
|
}
|
|
266
305
|
|
|
@@ -268,13 +307,25 @@ async function exportTaxonomies(db: Kysely<Database>): Promise<SeedTaxonomy[]> {
|
|
|
268
307
|
* Export menus with their items
|
|
269
308
|
*/
|
|
270
309
|
async function exportMenus(db: Kysely<Database>): Promise<SeedMenu[]> {
|
|
271
|
-
|
|
272
|
-
|
|
310
|
+
const i18nEnabled = isI18nEnabled();
|
|
311
|
+
|
|
312
|
+
const menus = await db
|
|
313
|
+
.selectFrom("_emdash_menus")
|
|
314
|
+
.selectAll()
|
|
315
|
+
.orderBy(["name", "locale"])
|
|
316
|
+
.execute();
|
|
273
317
|
|
|
274
318
|
const result: SeedMenu[] = [];
|
|
319
|
+
// translation_group -> seed-local id of the anchor menu in that group.
|
|
320
|
+
const groupToSeedId = new Map<string, string>();
|
|
321
|
+
// Shared across menus: translated items reference anchor items in sibling menus.
|
|
322
|
+
const itemGroupToSeedId = new Map<string, string>();
|
|
323
|
+
const usedItemSeedIds = new Set<string>();
|
|
275
324
|
|
|
276
325
|
for (const menu of menus) {
|
|
277
|
-
|
|
326
|
+
const seedId =
|
|
327
|
+
i18nEnabled && menu.locale ? `menu:${menu.name}:${menu.locale}` : `menu:${menu.name}`;
|
|
328
|
+
|
|
278
329
|
const items = await db
|
|
279
330
|
.selectFrom("_emdash_menu_items")
|
|
280
331
|
.selectAll()
|
|
@@ -282,16 +333,36 @@ async function exportMenus(db: Kysely<Database>): Promise<SeedMenu[]> {
|
|
|
282
333
|
.orderBy("sort_order", "asc")
|
|
283
334
|
.execute();
|
|
284
335
|
|
|
285
|
-
|
|
286
|
-
|
|
336
|
+
const seedItems = buildMenuItemTree(items, {
|
|
337
|
+
i18nEnabled,
|
|
338
|
+
menuName: menu.name,
|
|
339
|
+
menuLocale: menu.locale ?? null,
|
|
340
|
+
itemGroupToSeedId,
|
|
341
|
+
usedItemSeedIds,
|
|
342
|
+
});
|
|
287
343
|
|
|
288
|
-
|
|
344
|
+
const seedMenu: SeedMenu = {
|
|
345
|
+
id: seedId,
|
|
289
346
|
name: menu.name,
|
|
290
347
|
label: menu.label,
|
|
291
348
|
items: seedItems,
|
|
292
|
-
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
if (i18nEnabled && menu.locale) {
|
|
352
|
+
seedMenu.locale = menu.locale;
|
|
353
|
+
if (menu.translation_group) {
|
|
354
|
+
const anchor = groupToSeedId.get(menu.translation_group);
|
|
355
|
+
if (anchor) seedMenu.translationOf = anchor;
|
|
356
|
+
else groupToSeedId.set(menu.translation_group, seedId);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
result.push(seedMenu);
|
|
293
361
|
}
|
|
294
362
|
|
|
363
|
+
// Anchors first so import can resolve `translationOf`.
|
|
364
|
+
result.sort((a, b) => Number(!!a.translationOf) - Number(!!b.translationOf));
|
|
365
|
+
|
|
295
366
|
return result;
|
|
296
367
|
}
|
|
297
368
|
|
|
@@ -315,7 +386,17 @@ function buildMenuItemTree(
|
|
|
315
386
|
target: string | null;
|
|
316
387
|
title_attr: string | null;
|
|
317
388
|
css_classes: string | null;
|
|
389
|
+
locale?: string | null;
|
|
390
|
+
translation_group?: string | null;
|
|
318
391
|
}>,
|
|
392
|
+
i18nCtx: {
|
|
393
|
+
i18nEnabled: boolean;
|
|
394
|
+
menuName: string;
|
|
395
|
+
menuLocale: string | null;
|
|
396
|
+
// translation_group -> seed-local id of the anchor item in that group.
|
|
397
|
+
itemGroupToSeedId: Map<string, string>;
|
|
398
|
+
usedItemSeedIds: Set<string>;
|
|
399
|
+
},
|
|
319
400
|
): SeedMenuItem[] {
|
|
320
401
|
// Build parent -> children map
|
|
321
402
|
const childMap = new Map<string | null, typeof items>();
|
|
@@ -328,10 +409,28 @@ function buildMenuItemTree(
|
|
|
328
409
|
childMap.get(parentId)!.push(item);
|
|
329
410
|
}
|
|
330
411
|
|
|
412
|
+
function makeSeedId(item: (typeof items)[number]): string {
|
|
413
|
+
const base = slugify(item.label || "") || item.id;
|
|
414
|
+
const locale = i18nCtx.i18nEnabled ? (item.locale ?? i18nCtx.menuLocale) : null;
|
|
415
|
+
const candidate = locale
|
|
416
|
+
? `item:${i18nCtx.menuName}:${base}:${locale}`
|
|
417
|
+
: `item:${i18nCtx.menuName}:${base}`;
|
|
418
|
+
if (!i18nCtx.usedItemSeedIds.has(candidate)) {
|
|
419
|
+
i18nCtx.usedItemSeedIds.add(candidate);
|
|
420
|
+
return candidate;
|
|
421
|
+
}
|
|
422
|
+
// Collision fallback: append DB id to disambiguate duplicate labels.
|
|
423
|
+
const fallback = locale
|
|
424
|
+
? `item:${i18nCtx.menuName}:${base}:${item.id}:${locale}`
|
|
425
|
+
: `item:${i18nCtx.menuName}:${base}:${item.id}`;
|
|
426
|
+
i18nCtx.usedItemSeedIds.add(fallback);
|
|
427
|
+
return fallback;
|
|
428
|
+
}
|
|
429
|
+
|
|
331
430
|
// Recursively build tree
|
|
332
431
|
function buildLevel(parentId: string | null): SeedMenuItem[] {
|
|
333
432
|
const children = childMap.get(parentId) || [];
|
|
334
|
-
|
|
433
|
+
const result = children.map((item) => {
|
|
335
434
|
const seedItem: SeedMenuItem = {
|
|
336
435
|
type: item.type,
|
|
337
436
|
label: item.label || undefined,
|
|
@@ -354,6 +453,18 @@ function buildMenuItemTree(
|
|
|
354
453
|
seedItem.cssClasses = item.css_classes;
|
|
355
454
|
}
|
|
356
455
|
|
|
456
|
+
if (i18nCtx.i18nEnabled) {
|
|
457
|
+
const itemLocale = item.locale ?? i18nCtx.menuLocale;
|
|
458
|
+
const seedId = makeSeedId(item);
|
|
459
|
+
seedItem.id = seedId;
|
|
460
|
+
if (itemLocale) seedItem.locale = itemLocale;
|
|
461
|
+
if (item.translation_group) {
|
|
462
|
+
const anchor = i18nCtx.itemGroupToSeedId.get(item.translation_group);
|
|
463
|
+
if (anchor && anchor !== seedId) seedItem.translationOf = anchor;
|
|
464
|
+
else if (!anchor) i18nCtx.itemGroupToSeedId.set(item.translation_group, seedId);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
357
468
|
// Add children
|
|
358
469
|
const itemChildren = buildLevel(item.id);
|
|
359
470
|
if (itemChildren.length > 0) {
|
|
@@ -362,6 +473,10 @@ function buildMenuItemTree(
|
|
|
362
473
|
|
|
363
474
|
return seedItem;
|
|
364
475
|
});
|
|
476
|
+
|
|
477
|
+
// Sibling order is preserved (maps to sort_order on import). Cross-menu
|
|
478
|
+
// `translationOf` already resolves because exportMenus sorts anchors first.
|
|
479
|
+
return result;
|
|
365
480
|
}
|
|
366
481
|
|
|
367
482
|
return buildLevel(null);
|