emdash 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/dist/{apply-kC39ev1Z.mjs → apply-Bqoekfbe.mjs} +57 -10
  2. package/dist/apply-Bqoekfbe.mjs.map +1 -0
  3. package/dist/astro/index.d.mts +23 -9
  4. package/dist/astro/index.d.mts.map +1 -1
  5. package/dist/astro/index.mjs +90 -25
  6. package/dist/astro/index.mjs.map +1 -1
  7. package/dist/astro/middleware/auth.d.mts +3 -3
  8. package/dist/astro/middleware/auth.d.mts.map +1 -1
  9. package/dist/astro/middleware/auth.mjs +126 -55
  10. package/dist/astro/middleware/auth.mjs.map +1 -1
  11. package/dist/astro/middleware/redirect.mjs +2 -2
  12. package/dist/astro/middleware/request-context.mjs +1 -1
  13. package/dist/astro/middleware.d.mts.map +1 -1
  14. package/dist/astro/middleware.mjs +80 -41
  15. package/dist/astro/middleware.mjs.map +1 -1
  16. package/dist/astro/types.d.mts +27 -6
  17. package/dist/astro/types.d.mts.map +1 -1
  18. package/dist/{byline-CL847F26.mjs → byline-BGj9p9Ht.mjs} +53 -31
  19. package/dist/byline-BGj9p9Ht.mjs.map +1 -0
  20. package/dist/{bylines-C2a-2TGt.mjs → bylines-BihaoIDY.mjs} +12 -10
  21. package/dist/{bylines-C2a-2TGt.mjs.map → bylines-BihaoIDY.mjs.map} +1 -1
  22. package/dist/cli/index.mjs +17 -14
  23. package/dist/cli/index.mjs.map +1 -1
  24. package/dist/{config-CKE8p9xM.mjs → config-Cq8H0SfX.mjs} +2 -10
  25. package/dist/{config-CKE8p9xM.mjs.map → config-Cq8H0SfX.mjs.map} +1 -1
  26. package/dist/{content-D6C2WsZC.mjs → content-BsBoyj8G.mjs} +35 -5
  27. package/dist/content-BsBoyj8G.mjs.map +1 -0
  28. package/dist/db/index.mjs +2 -2
  29. package/dist/{default-Cyi4aAxu.mjs → default-WYlzADZL.mjs} +1 -1
  30. package/dist/{default-Cyi4aAxu.mjs.map → default-WYlzADZL.mjs.map} +1 -1
  31. package/dist/{dialect-helpers-B9uSp2GJ.mjs → dialect-helpers-DhTzaUxP.mjs} +4 -1
  32. package/dist/dialect-helpers-DhTzaUxP.mjs.map +1 -0
  33. package/dist/{error-Cxz0tQeO.mjs → error-DrxtnGPg.mjs} +1 -1
  34. package/dist/{error-Cxz0tQeO.mjs.map → error-DrxtnGPg.mjs.map} +1 -1
  35. package/dist/{index-CLBc4gw-.d.mts → index-Cff7AimE.d.mts} +77 -15
  36. package/dist/index-Cff7AimE.d.mts.map +1 -0
  37. package/dist/index.d.mts +6 -6
  38. package/dist/index.mjs +19 -19
  39. package/dist/{load-yOOlckBj.mjs → load-Veizk2cT.mjs} +1 -1
  40. package/dist/{load-yOOlckBj.mjs.map → load-Veizk2cT.mjs.map} +1 -1
  41. package/dist/{loader-fz8Q_3EO.mjs → loader-BmYdf3Dr.mjs} +4 -2
  42. package/dist/loader-BmYdf3Dr.mjs.map +1 -0
  43. package/dist/{manifest-schema-CL8DWO9b.mjs → manifest-schema-CuMio1A9.mjs} +1 -1
  44. package/dist/{manifest-schema-CL8DWO9b.mjs.map → manifest-schema-CuMio1A9.mjs.map} +1 -1
  45. package/dist/media/local-runtime.d.mts +4 -4
  46. package/dist/page/index.d.mts +10 -1
  47. package/dist/page/index.d.mts.map +1 -1
  48. package/dist/page/index.mjs +8 -4
  49. package/dist/page/index.mjs.map +1 -1
  50. package/dist/plugins/adapt-sandbox-entry.d.mts +3 -3
  51. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  52. package/dist/{query-BVYN0PJ6.mjs → query-sesiOndV.mjs} +20 -8
  53. package/dist/{query-BVYN0PJ6.mjs.map → query-sesiOndV.mjs.map} +1 -1
  54. package/dist/{redirect-DIfIni3r.mjs → redirect-DUAk-Yl_.mjs} +9 -2
  55. package/dist/redirect-DUAk-Yl_.mjs.map +1 -0
  56. package/dist/{registry-BNYQKX_d.mjs → registry-DU18yVo0.mjs} +14 -4
  57. package/dist/registry-DU18yVo0.mjs.map +1 -0
  58. package/dist/{runner-BraqvGYk.mjs → runner-Biufrii2.mjs} +157 -132
  59. package/dist/runner-Biufrii2.mjs.map +1 -0
  60. package/dist/runner-EAtf0ZIe.d.mts.map +1 -1
  61. package/dist/runtime.d.mts +3 -3
  62. package/dist/runtime.mjs +2 -2
  63. package/dist/{search-C1gg67nN.mjs → search-BXB-jfu2.mjs} +241 -109
  64. package/dist/search-BXB-jfu2.mjs.map +1 -0
  65. package/dist/seed/index.d.mts +1 -1
  66. package/dist/seed/index.mjs +10 -10
  67. package/dist/seo/index.d.mts +1 -1
  68. package/dist/storage/local.d.mts +1 -1
  69. package/dist/storage/local.mjs +1 -1
  70. package/dist/storage/s3.d.mts +11 -3
  71. package/dist/storage/s3.d.mts.map +1 -1
  72. package/dist/storage/s3.mjs +76 -15
  73. package/dist/storage/s3.mjs.map +1 -1
  74. package/dist/{tokens-DpgrkrXK.mjs → tokens-DrB-W6Q-.mjs} +1 -1
  75. package/dist/{tokens-DpgrkrXK.mjs.map → tokens-DrB-W6Q-.mjs.map} +1 -1
  76. package/dist/{types-BRuPJGdV.d.mts → types-BbsYgi_R.d.mts} +3 -1
  77. package/dist/types-BbsYgi_R.d.mts.map +1 -0
  78. package/dist/{types-CUBbjgmP.mjs → types-Bec-r_3_.mjs} +1 -1
  79. package/dist/types-Bec-r_3_.mjs.map +1 -0
  80. package/dist/{types-DaNLHo_T.d.mts → types-C1-PVaS_.d.mts} +14 -6
  81. package/dist/types-C1-PVaS_.d.mts.map +1 -0
  82. package/dist/types-CMMN0pNg.mjs.map +1 -1
  83. package/dist/{types-BQo5JS0J.d.mts → types-CaKte3hR.d.mts} +78 -6
  84. package/dist/types-CaKte3hR.d.mts.map +1 -0
  85. package/dist/{types-CiA5Gac0.mjs → types-DuNbGKjF.mjs} +1 -1
  86. package/dist/{types-CiA5Gac0.mjs.map → types-DuNbGKjF.mjs.map} +1 -1
  87. package/dist/{validate-_rsF-Dx_.mjs → validate-CXnRKfJK.mjs} +2 -2
  88. package/dist/{validate-_rsF-Dx_.mjs.map → validate-CXnRKfJK.mjs.map} +1 -1
  89. package/dist/{validate-CqRJb_xU.mjs → validate-VPnKoIzW.mjs} +11 -11
  90. package/dist/{validate-CqRJb_xU.mjs.map → validate-VPnKoIzW.mjs.map} +1 -1
  91. package/dist/{validate-HtxZeaBi.d.mts → validate-bfg9OR6N.d.mts} +2 -2
  92. package/dist/{validate-HtxZeaBi.d.mts.map → validate-bfg9OR6N.d.mts.map} +1 -1
  93. package/dist/version-REAapfsU.mjs +7 -0
  94. package/dist/version-REAapfsU.mjs.map +1 -0
  95. package/package.json +6 -6
  96. package/src/api/csrf.ts +13 -2
  97. package/src/api/handlers/content.ts +7 -0
  98. package/src/api/handlers/dashboard.ts +4 -8
  99. package/src/api/handlers/device-flow.ts +55 -37
  100. package/src/api/handlers/index.ts +6 -1
  101. package/src/api/handlers/redirects.ts +95 -3
  102. package/src/api/handlers/seo.ts +48 -21
  103. package/src/api/public-url.ts +84 -0
  104. package/src/api/schemas/content.ts +2 -2
  105. package/src/api/schemas/menus.ts +12 -2
  106. package/src/api/schemas/redirects.ts +1 -0
  107. package/src/astro/integration/index.ts +30 -7
  108. package/src/astro/integration/routes.ts +13 -2
  109. package/src/astro/integration/runtime.ts +7 -5
  110. package/src/astro/integration/vite-config.ts +55 -9
  111. package/src/astro/middleware/auth.ts +60 -56
  112. package/src/astro/middleware/csp.ts +25 -0
  113. package/src/astro/middleware.ts +31 -3
  114. package/src/astro/routes/PluginRegistry.tsx +8 -2
  115. package/src/astro/routes/admin.astro +7 -2
  116. package/src/astro/routes/api/admin/users/[id]/disable.ts +18 -12
  117. package/src/astro/routes/api/admin/users/[id]/index.ts +26 -5
  118. package/src/astro/routes/api/auth/invite/complete.ts +3 -2
  119. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +2 -1
  120. package/src/astro/routes/api/auth/oauth/[provider].ts +2 -1
  121. package/src/astro/routes/api/auth/passkey/options.ts +3 -2
  122. package/src/astro/routes/api/auth/passkey/register/options.ts +3 -2
  123. package/src/astro/routes/api/auth/passkey/register/verify.ts +3 -2
  124. package/src/astro/routes/api/auth/passkey/verify.ts +3 -2
  125. package/src/astro/routes/api/auth/signup/complete.ts +3 -2
  126. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -0
  127. package/src/astro/routes/api/content/[collection]/index.ts +31 -3
  128. package/src/astro/routes/api/import/wordpress/execute.ts +9 -0
  129. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +2 -0
  130. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +10 -0
  131. package/src/astro/routes/api/manifest.ts +4 -1
  132. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +7 -2
  133. package/src/astro/routes/api/oauth/authorize.ts +12 -7
  134. package/src/astro/routes/api/oauth/device/code.ts +5 -1
  135. package/src/astro/routes/api/setup/admin-verify.ts +3 -2
  136. package/src/astro/routes/api/setup/admin.ts +3 -2
  137. package/src/astro/routes/api/setup/dev-bypass.ts +2 -1
  138. package/src/astro/routes/api/setup/index.ts +3 -2
  139. package/src/astro/routes/api/snapshot.ts +2 -1
  140. package/src/astro/routes/api/themes/preview.ts +2 -1
  141. package/src/astro/routes/api/well-known/auth.ts +1 -0
  142. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +3 -2
  143. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +3 -2
  144. package/src/astro/routes/robots.txt.ts +5 -1
  145. package/src/astro/routes/sitemap-[collection].xml.ts +104 -0
  146. package/src/astro/routes/sitemap.xml.ts +18 -23
  147. package/src/astro/storage/adapters.ts +19 -5
  148. package/src/astro/storage/types.ts +12 -4
  149. package/src/astro/types.ts +28 -1
  150. package/src/auth/passkey-config.ts +6 -10
  151. package/src/bylines/index.ts +13 -10
  152. package/src/cli/commands/login.ts +5 -2
  153. package/src/components/InlinePortableTextEditor.tsx +5 -3
  154. package/src/content/converters/portable-text-to-prosemirror.ts +50 -2
  155. package/src/database/dialect-helpers.ts +3 -0
  156. package/src/database/migrations/034_published_at_index.ts +29 -0
  157. package/src/database/migrations/runner.ts +2 -0
  158. package/src/database/repositories/byline.ts +48 -42
  159. package/src/database/repositories/content.ts +28 -1
  160. package/src/database/repositories/options.ts +9 -3
  161. package/src/database/repositories/redirect.ts +13 -0
  162. package/src/database/repositories/seo.ts +34 -17
  163. package/src/database/repositories/types.ts +2 -0
  164. package/src/database/validate.ts +10 -10
  165. package/src/emdash-runtime.ts +66 -19
  166. package/src/import/index.ts +1 -1
  167. package/src/import/sources/wxr.ts +45 -2
  168. package/src/index.ts +10 -1
  169. package/src/loader.ts +2 -0
  170. package/src/mcp/server.ts +85 -5
  171. package/src/menus/index.ts +6 -1
  172. package/src/page/context.ts +13 -1
  173. package/src/page/jsonld.ts +10 -6
  174. package/src/page/seo-contributions.ts +1 -1
  175. package/src/plugins/context.ts +145 -35
  176. package/src/plugins/manager.ts +12 -0
  177. package/src/plugins/types.ts +80 -4
  178. package/src/query.ts +18 -0
  179. package/src/redirects/loops.ts +318 -0
  180. package/src/schema/registry.ts +8 -0
  181. package/src/search/fts-manager.ts +4 -0
  182. package/src/settings/index.ts +64 -0
  183. package/src/storage/s3.ts +94 -25
  184. package/src/storage/types.ts +13 -5
  185. package/src/utils/chunks.ts +17 -0
  186. package/src/utils/slugify.ts +11 -0
  187. package/src/version.ts +12 -0
  188. package/dist/apply-kC39ev1Z.mjs.map +0 -1
  189. package/dist/byline-CL847F26.mjs.map +0 -1
  190. package/dist/content-D6C2WsZC.mjs.map +0 -1
  191. package/dist/dialect-helpers-B9uSp2GJ.mjs.map +0 -1
  192. package/dist/index-CLBc4gw-.d.mts.map +0 -1
  193. package/dist/loader-fz8Q_3EO.mjs.map +0 -1
  194. package/dist/redirect-DIfIni3r.mjs.map +0 -1
  195. package/dist/registry-BNYQKX_d.mjs.map +0 -1
  196. package/dist/runner-BraqvGYk.mjs.map +0 -1
  197. package/dist/search-C1gg67nN.mjs.map +0 -1
  198. package/dist/types-BQo5JS0J.d.mts.map +0 -1
  199. package/dist/types-BRuPJGdV.d.mts.map +0 -1
  200. package/dist/types-CUBbjgmP.mjs.map +0 -1
  201. package/dist/types-DaNLHo_T.d.mts.map +0 -1
  202. /package/src/astro/routes/api/media/file/{[key].ts → [...key].ts} +0 -0
@@ -1,17 +1,17 @@
1
- import { o as jsonExtractExpr } from "./dialect-helpers-B9uSp2GJ.mjs";
2
- import { n as validateJsonFieldName, r as validatePluginIdentifier, t as validateIdentifier } from "./validate-CqRJb_xU.mjs";
3
- import { i as slugify, r as RevisionRepository, t as ContentRepository } from "./content-D6C2WsZC.mjs";
1
+ import { n as validateJsonFieldName, r as validatePluginIdentifier, t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
2
+ import { o as jsonExtractExpr } from "./dialect-helpers-DhTzaUxP.mjs";
3
+ import { a as slugify, r as RevisionRepository, t as ContentRepository } from "./content-BsBoyj8G.mjs";
4
4
  import { r as encodeBase64, t as decodeBase64 } from "./base64-MBPo9ozB.mjs";
5
5
  import { n as decodeCursor, r as encodeCursor, t as EmDashValidationError } from "./types-CMMN0pNg.mjs";
6
6
  import { t as MediaRepository } from "./media-DqHVh136.mjs";
7
- import { a as stripCredentialHeaders, i as ssrfSafeFetch, o as validateExternalUrl, r as SsrfError, u as OptionsRepository } from "./apply-kC39ev1Z.mjs";
8
- import { a as withTransaction, i as FTSManager, n as SchemaRegistry } from "./registry-BNYQKX_d.mjs";
9
- import { t as RedirectRepository } from "./redirect-DIfIni3r.mjs";
10
- import { t as BylineRepository } from "./byline-CL847F26.mjs";
11
- import { i as isI18nEnabled } from "./config-CKE8p9xM.mjs";
12
- import { n as getDb } from "./loader-fz8Q_3EO.mjs";
13
- import { i as pluginManifestSchema } from "./manifest-schema-CL8DWO9b.mjs";
14
- import { t as generatePreviewToken } from "./tokens-DpgrkrXK.mjs";
7
+ import { a as stripCredentialHeaders, f as OptionsRepository, i as ssrfSafeFetch, o as validateExternalUrl, r as SsrfError } from "./apply-Bqoekfbe.mjs";
8
+ import { a as withTransaction, i as FTSManager, n as SchemaRegistry } from "./registry-DU18yVo0.mjs";
9
+ import { t as RedirectRepository } from "./redirect-DUAk-Yl_.mjs";
10
+ import { n as SQL_BATCH_SIZE, r as chunks, t as BylineRepository } from "./byline-BGj9p9Ht.mjs";
11
+ import { r as isI18nEnabled } from "./config-Cq8H0SfX.mjs";
12
+ import { n as getDb } from "./loader-BmYdf3Dr.mjs";
13
+ import { i as pluginManifestSchema } from "./manifest-schema-CuMio1A9.mjs";
14
+ import { t as generatePreviewToken } from "./tokens-DrB-W6Q-.mjs";
15
15
  import { sql } from "kysely";
16
16
  import { ulid } from "ulidx";
17
17
  import { z } from "astro/zod";
@@ -827,6 +827,13 @@ var SeoRepository = class {
827
827
  this.db = db;
828
828
  }
829
829
  /**
830
+ * Check whether a collection has SEO enabled (`has_seo = 1`).
831
+ * Returns `false` if the collection does not exist.
832
+ */
833
+ async isEnabled(collection) {
834
+ return (await this.db.selectFrom("_emdash_collections").select("has_seo").where("slug", "=", collection).executeTakeFirst())?.has_seo === 1;
835
+ }
836
+ /**
830
837
  * Get SEO data for a content item. Returns null defaults if no row exists.
831
838
  */
832
839
  async get(collection, contentId) {
@@ -841,24 +848,27 @@ var SeoRepository = class {
841
848
  };
842
849
  }
843
850
  /**
844
- * Get SEO data for multiple content items in a single query.
851
+ * Get SEO data for multiple content items.
845
852
  * Returns a Map keyed by content_id. Items without SEO rows get defaults.
853
+ *
854
+ * Chunks the `content_id IN (…)` clause so the total bound-parameter count
855
+ * per statement (ids + the `collection = ?` filter) stays within Cloudflare
856
+ * D1's 100-variable limit regardless of how many content items are passed.
846
857
  */
847
858
  async getMany(collection, contentIds) {
848
859
  const result = /* @__PURE__ */ new Map();
849
860
  if (contentIds.length === 0) return result;
850
- const rows = await this.db.selectFrom("_emdash_seo").selectAll().where("collection", "=", collection).where("content_id", "in", contentIds).execute();
851
- const rowMap = new Map(rows.map((r) => [r.content_id, r]));
852
- for (const id of contentIds) {
853
- const row = rowMap.get(id);
854
- if (row) result.set(id, {
861
+ for (const id of contentIds) result.set(id, { ...SEO_DEFAULTS$1 });
862
+ const uniqueContentIds = [...new Set(contentIds)];
863
+ for (const chunk of chunks(uniqueContentIds, SQL_BATCH_SIZE)) {
864
+ const rows = await this.db.selectFrom("_emdash_seo").selectAll().where("collection", "=", collection).where("content_id", "in", chunk).execute();
865
+ for (const row of rows) result.set(row.content_id, {
855
866
  title: row.seo_title ?? null,
856
867
  description: row.seo_description ?? null,
857
868
  image: row.seo_image ?? null,
858
869
  canonical: row.seo_canonical ?? null,
859
870
  noIndex: row.seo_no_index === 1
860
871
  });
861
- else result.set(id, { ...SEO_DEFAULTS$1 });
862
872
  }
863
873
  return result;
864
874
  }
@@ -1220,7 +1230,9 @@ async function handleContentCreate(db, collection, body) {
1220
1230
  status: body.status || "draft",
1221
1231
  authorId: body.authorId,
1222
1232
  locale: body.locale,
1223
- translationOf: body.translationOf
1233
+ translationOf: body.translationOf,
1234
+ createdAt: body.createdAt,
1235
+ publishedAt: body.publishedAt
1224
1236
  });
1225
1237
  if (body.bylines !== void 0) {
1226
1238
  await bylineRepo.setContentBylines(collection, created.id, body.bylines);
@@ -1454,6 +1466,7 @@ async function handleContentPermanentDelete(db, collection, id) {
1454
1466
  if (wasDeleted) {
1455
1467
  await new SeoRepository(trx).delete(collection, resolvedId);
1456
1468
  await new CommentRepository(trx).deleteByContent(collection, resolvedId);
1469
+ await new RevisionRepository(trx).deleteByEntry(collection, resolvedId);
1457
1470
  }
1458
1471
  return wasDeleted;
1459
1472
  })) return {
@@ -2629,7 +2642,7 @@ const contentListQuery = cursorPaginationQuery.extend({
2629
2642
  const contentCreateBody = z$1.object({
2630
2643
  data: z$1.record(z$1.string(), z$1.unknown()),
2631
2644
  slug: z$1.string().nullish(),
2632
- status: z$1.string().optional(),
2645
+ status: z$1.enum(["draft"]).optional(),
2633
2646
  bylines: z$1.array(contentBylineInputSchema).optional(),
2634
2647
  locale: localeCode.optional(),
2635
2648
  translationOf: z$1.string().optional(),
@@ -2638,7 +2651,7 @@ const contentCreateBody = z$1.object({
2638
2651
  const contentUpdateBody = z$1.object({
2639
2652
  data: z$1.record(z$1.string(), z$1.unknown()).optional(),
2640
2653
  slug: z$1.string().nullish(),
2641
- status: z$1.string().optional(),
2654
+ status: z$1.enum(["draft"]).optional(),
2642
2655
  authorId: z$1.string().nullish(),
2643
2656
  bylines: z$1.array(contentBylineInputSchema).optional(),
2644
2657
  _rev: z$1.string().optional().meta({ description: "Opaque revision token for optimistic concurrency" }),
@@ -3111,9 +3124,58 @@ const passkeyRegisterVerifyBody = z$1.object({
3111
3124
  const passkeyRenameBody = z$1.object({ name: z$1.string().min(1) }).meta({ id: "PasskeyRenameBody" });
3112
3125
  const authMeActionBody = z$1.object({ action: z$1.string().min(1) }).meta({ id: "AuthMeActionBody" });
3113
3126
 
3127
+ //#endregion
3128
+ //#region src/utils/url.ts
3129
+ /**
3130
+ * URL scheme validation utilities
3131
+ *
3132
+ * Prevents XSS via dangerous URL schemes (javascript:, data:, vbscript:, etc.)
3133
+ * by allowlisting known-safe schemes before rendering into href attributes.
3134
+ */
3135
+ /**
3136
+ * Matches URLs that are safe to render in href attributes.
3137
+ *
3138
+ * Allowed:
3139
+ * - http:// and https://
3140
+ * - mailto: and tel:
3141
+ * - Relative paths (starting with /)
3142
+ * - Fragment links (starting with #)
3143
+ * - Protocol-relative URLs are NOT allowed (starting with //) as they can
3144
+ * redirect to attacker-controlled hosts.
3145
+ */
3146
+ const SAFE_URL_SCHEME_RE = /^(https?:|mailto:|tel:|\/(?!\/)|#)/i;
3147
+ /**
3148
+ * Returns the URL unchanged if it uses a safe scheme, otherwise returns "#".
3149
+ *
3150
+ * Use this at the render layer as the primary defense against XSS via
3151
+ * dangerous URL schemes like `javascript:`, `data:`, or `vbscript:`.
3152
+ *
3153
+ * @example
3154
+ * ```ts
3155
+ * sanitizeHref("https://example.com") // "https://example.com"
3156
+ * sanitizeHref("/about") // "/about"
3157
+ * sanitizeHref("#section") // "#section"
3158
+ * sanitizeHref("mailto:a@b.com") // "mailto:a@b.com"
3159
+ * sanitizeHref("javascript:alert(1)") // "#"
3160
+ * sanitizeHref("data:text/html,<script>") // "#"
3161
+ * sanitizeHref("") // "#"
3162
+ * ```
3163
+ */
3164
+ function sanitizeHref(url) {
3165
+ if (!url) return "#";
3166
+ return SAFE_URL_SCHEME_RE.test(url) ? url : "#";
3167
+ }
3168
+ /**
3169
+ * Returns true if the URL uses a safe scheme for rendering in href attributes.
3170
+ */
3171
+ function isSafeHref(url) {
3172
+ return SAFE_URL_SCHEME_RE.test(url);
3173
+ }
3174
+
3114
3175
  //#endregion
3115
3176
  //#region src/api/schemas/menus.ts
3116
3177
  const menuItemType = z$1.string().min(1);
3178
+ const safeHref = z$1.string().trim().refine(isSafeHref, "URL must use http, https, mailto, tel, a relative path, or a fragment identifier");
3117
3179
  const createMenuBody = z$1.object({
3118
3180
  name: z$1.string().min(1),
3119
3181
  label: z$1.string().min(1)
@@ -3124,7 +3186,7 @@ const createMenuItemBody = z$1.object({
3124
3186
  label: z$1.string().min(1),
3125
3187
  referenceCollection: z$1.string().optional(),
3126
3188
  referenceId: z$1.string().optional(),
3127
- customUrl: z$1.string().optional(),
3189
+ customUrl: safeHref.optional(),
3128
3190
  target: z$1.string().optional(),
3129
3191
  titleAttr: z$1.string().optional(),
3130
3192
  cssClasses: z$1.string().optional(),
@@ -3133,7 +3195,7 @@ const createMenuItemBody = z$1.object({
3133
3195
  }).meta({ id: "CreateMenuItemBody" });
3134
3196
  const updateMenuItemBody = z$1.object({
3135
3197
  label: z$1.string().min(1).optional(),
3136
- customUrl: z$1.string().optional(),
3198
+ customUrl: safeHref.optional(),
3137
3199
  target: z$1.string().optional(),
3138
3200
  titleAttr: z$1.string().optional(),
3139
3201
  cssClasses: z$1.string().optional(),
@@ -3600,7 +3662,8 @@ const redirectSchema = z$1.object({
3600
3662
  }).meta({ id: "Redirect" });
3601
3663
  const redirectListResponseSchema = z$1.object({
3602
3664
  items: z$1.array(redirectSchema),
3603
- nextCursor: z$1.string().optional()
3665
+ nextCursor: z$1.string().optional(),
3666
+ loopRedirectIds: z$1.array(z$1.string()).optional()
3604
3667
  }).meta({ id: "RedirectListResponse" });
3605
3668
  const notFoundEntrySchema = z$1.object({
3606
3669
  id: z$1.string(),
@@ -3918,10 +3981,13 @@ function isTextBlock(block) {
3918
3981
  return block._type === "block";
3919
3982
  }
3920
3983
  /**
3921
- * Type guard for image blocks
3984
+ * Type guard for image blocks.
3985
+ * Checks both `_type` and that `asset` is a valid object — image blocks
3986
+ * without an `asset` wrapper (e.g. `{ _type: "image", url: "..." }`) are
3987
+ * malformed and should not be cast to `PortableTextImageBlock`.
3922
3988
  */
3923
3989
  function isImageBlock(block) {
3924
- return block._type === "image";
3990
+ return block._type === "image" && "asset" in block && typeof block.asset === "object" && block.asset !== null;
3925
3991
  }
3926
3992
  /**
3927
3993
  * Type guard for code blocks
@@ -3935,6 +4001,7 @@ function isCodeBlock(block) {
3935
4001
  function convertBlock(block) {
3936
4002
  if (isTextBlock(block)) return convertTextBlock(block);
3937
4003
  if (isImageBlock(block)) return convertImage(block);
4004
+ if (block._type === "image") return convertMalformedImage(block);
3938
4005
  if (isCodeBlock(block)) return convertCodeBlock(block);
3939
4006
  if (block._type === "break") return { type: "horizontalRule" };
3940
4007
  return {
@@ -4119,6 +4186,27 @@ function convertImage(block) {
4119
4186
  };
4120
4187
  }
4121
4188
  /**
4189
+ * Convert a malformed image block (missing `asset` wrapper) to ProseMirror.
4190
+ * Handles blocks like `{ _type: "image", url: "...", alt: "..." }` that may
4191
+ * originate from migrations or third-party imports.
4192
+ */
4193
+ function convertMalformedImage(block) {
4194
+ return {
4195
+ type: "image",
4196
+ attrs: {
4197
+ src: "url" in block && typeof block.url === "string" ? block.url : "",
4198
+ alt: "alt" in block && typeof block.alt === "string" ? block.alt : "",
4199
+ title: "caption" in block && typeof block.caption === "string" ? block.caption : "",
4200
+ mediaId: void 0,
4201
+ provider: void 0,
4202
+ width: "width" in block && typeof block.width === "number" ? block.width : void 0,
4203
+ height: "height" in block && typeof block.height === "number" ? block.height : void 0,
4204
+ displayWidth: "displayWidth" in block && typeof block.displayWidth === "number" ? block.displayWidth : void 0,
4205
+ displayHeight: "displayHeight" in block && typeof block.displayHeight === "number" ? block.displayHeight : void 0
4206
+ }
4207
+ };
4208
+ }
4209
+ /**
4122
4210
  * Convert code block to ProseMirror
4123
4211
  */
4124
4212
  function convertCodeBlock(block) {
@@ -4132,54 +4220,6 @@ function convertCodeBlock(block) {
4132
4220
  };
4133
4221
  }
4134
4222
 
4135
- //#endregion
4136
- //#region src/utils/url.ts
4137
- /**
4138
- * URL scheme validation utilities
4139
- *
4140
- * Prevents XSS via dangerous URL schemes (javascript:, data:, vbscript:, etc.)
4141
- * by allowlisting known-safe schemes before rendering into href attributes.
4142
- */
4143
- /**
4144
- * Matches URLs that are safe to render in href attributes.
4145
- *
4146
- * Allowed:
4147
- * - http:// and https://
4148
- * - mailto: and tel:
4149
- * - Relative paths (starting with /)
4150
- * - Fragment links (starting with #)
4151
- * - Protocol-relative URLs are NOT allowed (starting with //) as they can
4152
- * redirect to attacker-controlled hosts.
4153
- */
4154
- const SAFE_URL_SCHEME_RE = /^(https?:|mailto:|tel:|\/(?!\/)|#)/i;
4155
- /**
4156
- * Returns the URL unchanged if it uses a safe scheme, otherwise returns "#".
4157
- *
4158
- * Use this at the render layer as the primary defense against XSS via
4159
- * dangerous URL schemes like `javascript:`, `data:`, or `vbscript:`.
4160
- *
4161
- * @example
4162
- * ```ts
4163
- * sanitizeHref("https://example.com") // "https://example.com"
4164
- * sanitizeHref("/about") // "/about"
4165
- * sanitizeHref("#section") // "#section"
4166
- * sanitizeHref("mailto:a@b.com") // "mailto:a@b.com"
4167
- * sanitizeHref("javascript:alert(1)") // "#"
4168
- * sanitizeHref("data:text/html,<script>") // "#"
4169
- * sanitizeHref("") // "#"
4170
- * ```
4171
- */
4172
- function sanitizeHref(url) {
4173
- if (!url) return "#";
4174
- return SAFE_URL_SCHEME_RE.test(url) ? url : "#";
4175
- }
4176
- /**
4177
- * Returns true if the URL uses a safe scheme for rendering in href attributes.
4178
- */
4179
- function isSafeHref(url) {
4180
- return SAFE_URL_SCHEME_RE.test(url);
4181
- }
4182
-
4183
4223
  //#endregion
4184
4224
  //#region src/cli/wxr/parser.ts
4185
4225
  const PHP_SERIALIZED_STRING_PATTERN = /s:\d+:"([^"]+)"/g;
@@ -5327,21 +5367,45 @@ function createStorageAccess(db, pluginId, storageConfig) {
5327
5367
  return storage;
5328
5368
  }
5329
5369
  /**
5370
+ * Extract `seo` from a plugin-supplied content write input and return both
5371
+ * parts. Mutates nothing — returns a new field map without the `seo` key.
5372
+ */
5373
+ function splitSeoFromInput(input) {
5374
+ const { seo, ...fields } = input;
5375
+ if (seo !== void 0 && (seo === null || typeof seo !== "object" || Array.isArray(seo))) throw new Error("content.seo must be an object");
5376
+ return {
5377
+ fields,
5378
+ seo
5379
+ };
5380
+ }
5381
+ /**
5382
+ * Reject writing SEO to a collection that does not have it enabled.
5383
+ * Matches the REST API behavior (VALIDATION_ERROR).
5384
+ */
5385
+ async function assertSeoEnabled(seoRepo, collection, seo) {
5386
+ const hasSeo = await seoRepo.isEnabled(collection);
5387
+ if (seo !== void 0 && !hasSeo) throw new Error(`Collection "${collection}" does not have SEO enabled. Remove the seo field or enable SEO on this collection.`);
5388
+ return hasSeo;
5389
+ }
5390
+ /**
5330
5391
  * Create read-only content access
5331
5392
  */
5332
5393
  function createContentAccess(db) {
5333
5394
  const contentRepo = new ContentRepository(db);
5395
+ const seoRepo = new SeoRepository(db);
5334
5396
  return {
5335
5397
  async get(collection, id) {
5336
5398
  const item = await contentRepo.findById(collection, id);
5337
5399
  if (!item) return null;
5338
- return {
5400
+ const result = {
5339
5401
  id: item.id,
5340
5402
  type: item.type,
5341
5403
  data: item.data,
5342
5404
  createdAt: item.createdAt,
5343
5405
  updatedAt: item.updatedAt
5344
5406
  };
5407
+ if (await seoRepo.isEnabled(collection)) result.seo = await seoRepo.get(collection, item.id);
5408
+ return result;
5345
5409
  },
5346
5410
  async list(collection, options) {
5347
5411
  let orderBy;
@@ -5357,14 +5421,22 @@ function createContentAccess(db) {
5357
5421
  cursor: options?.cursor,
5358
5422
  orderBy
5359
5423
  });
5424
+ const items = result.items.map((item) => ({
5425
+ id: item.id,
5426
+ type: item.type,
5427
+ data: item.data,
5428
+ createdAt: item.createdAt,
5429
+ updatedAt: item.updatedAt
5430
+ }));
5431
+ if (items.length > 0 && await seoRepo.isEnabled(collection)) {
5432
+ const seoMap = await seoRepo.getMany(collection, items.map((i) => i.id));
5433
+ for (const item of items) {
5434
+ const seo = seoMap.get(item.id);
5435
+ if (seo) item.seo = seo;
5436
+ }
5437
+ }
5360
5438
  return {
5361
- items: result.items.map((item) => ({
5362
- id: item.id,
5363
- type: item.type,
5364
- data: item.data,
5365
- createdAt: item.createdAt,
5366
- updatedAt: item.updatedAt
5367
- })),
5439
+ items,
5368
5440
  cursor: result.nextCursor,
5369
5441
  hasMore: !!result.nextCursor
5370
5442
  };
@@ -5372,37 +5444,62 @@ function createContentAccess(db) {
5372
5444
  };
5373
5445
  }
5374
5446
  /**
5375
- * Create full content access with write operations
5447
+ * Create full content access with write operations.
5448
+ *
5449
+ * `create` and `update` accept a reserved `seo` key in their `data`
5450
+ * argument. When present, it is routed to the core SEO panel
5451
+ * (`_emdash_seo`) via `SeoRepository.upsert`, in the same transaction as
5452
+ * the content write. The returned `ContentItem.seo` reflects the resulting
5453
+ * SEO state for SEO-enabled collections.
5376
5454
  */
5377
5455
  function createContentAccessWithWrite(db) {
5378
- const contentRepo = new ContentRepository(db);
5379
5456
  return {
5380
5457
  ...createContentAccess(db),
5381
5458
  async create(collection, data) {
5382
- const item = await contentRepo.create({
5383
- type: collection,
5384
- data
5459
+ const { fields, seo } = splitSeoFromInput(data);
5460
+ return withTransaction(db, async (trx) => {
5461
+ const trxContentRepo = new ContentRepository(trx);
5462
+ const trxSeoRepo = new SeoRepository(trx);
5463
+ const hasSeo = await assertSeoEnabled(trxSeoRepo, collection, seo);
5464
+ const item = await trxContentRepo.create({
5465
+ type: collection,
5466
+ data: fields
5467
+ });
5468
+ const result = {
5469
+ id: item.id,
5470
+ type: item.type,
5471
+ data: item.data,
5472
+ createdAt: item.createdAt,
5473
+ updatedAt: item.updatedAt
5474
+ };
5475
+ if (hasSeo) result.seo = seo !== void 0 ? await trxSeoRepo.upsert(collection, item.id, seo) : await trxSeoRepo.get(collection, item.id);
5476
+ return result;
5385
5477
  });
5386
- return {
5387
- id: item.id,
5388
- type: item.type,
5389
- data: item.data,
5390
- createdAt: item.createdAt,
5391
- updatedAt: item.updatedAt
5392
- };
5393
5478
  },
5394
5479
  async update(collection, id, data) {
5395
- const item = await contentRepo.update(collection, id, { data });
5396
- return {
5397
- id: item.id,
5398
- type: item.type,
5399
- data: item.data,
5400
- createdAt: item.createdAt,
5401
- updatedAt: item.updatedAt
5402
- };
5480
+ const { fields, seo } = splitSeoFromInput(data);
5481
+ return withTransaction(db, async (trx) => {
5482
+ const trxContentRepo = new ContentRepository(trx);
5483
+ const trxSeoRepo = new SeoRepository(trx);
5484
+ const hasSeo = await assertSeoEnabled(trxSeoRepo, collection, seo);
5485
+ const item = Object.keys(fields).length > 0 ? await trxContentRepo.update(collection, id, { data: fields }) : await (async () => {
5486
+ const existing = await trxContentRepo.findById(collection, id);
5487
+ if (!existing) throw new Error("Content not found");
5488
+ return existing;
5489
+ })();
5490
+ const result = {
5491
+ id: item.id,
5492
+ type: item.type,
5493
+ data: item.data,
5494
+ createdAt: item.createdAt,
5495
+ updatedAt: item.updatedAt
5496
+ };
5497
+ if (hasSeo) result.seo = seo !== void 0 ? await trxSeoRepo.upsert(collection, item.id, seo) : await trxSeoRepo.get(collection, item.id);
5498
+ return result;
5499
+ });
5403
5500
  },
5404
5501
  async delete(collection, id) {
5405
- return contentRepo.delete(collection, id);
5502
+ return new ContentRepository(db).delete(collection, id);
5406
5503
  }
5407
5504
  };
5408
5505
  }
@@ -5494,6 +5591,7 @@ function createMediaAccessWithWrite(db, getUploadUrlFn, storage) {
5494
5591
  const MAX_PLUGIN_REDIRECTS = 5;
5495
5592
  function isHostAllowed(host, allowedHosts) {
5496
5593
  return allowedHosts.some((pattern) => {
5594
+ if (pattern === "*") return true;
5497
5595
  if (pattern.startsWith("*.")) {
5498
5596
  const suffix = pattern.slice(1);
5499
5597
  return host.endsWith(suffix) || host === pattern.slice(2);
@@ -7035,6 +7133,14 @@ var PluginManager = class {
7035
7133
  };
7036
7134
  }
7037
7135
  /**
7136
+ * Set the email pipeline used when creating plugin contexts.
7137
+ * Reinitializes routes/hooks if already initialized so ctx.email is available immediately.
7138
+ */
7139
+ setEmailPipeline(pipeline) {
7140
+ this.factoryOptions.emailPipeline = pipeline;
7141
+ if (this.initialized) this.reinitialize();
7142
+ }
7143
+ /**
7038
7144
  * Register a plugin definition
7039
7145
  * This resolves the definition and adds it to the manager, but doesn't install it
7040
7146
  */
@@ -7955,8 +8061,8 @@ function wxrPostToNormalizedItem(post, attachmentMap) {
7955
8061
  title: post.title || "Untitled",
7956
8062
  content,
7957
8063
  excerpt: post.excerpt,
7958
- date: post.postDate ? new Date(post.postDate) : /* @__PURE__ */ new Date(),
7959
- modified: post.postModified ? new Date(post.postModified) : void 0,
8064
+ date: parseWxrDate(post.postDateGmt, post.pubDate, post.postDate) ?? /* @__PURE__ */ new Date(),
8065
+ modified: parseWxrDate(post.postModifiedGmt, void 0, post.postModified),
7960
8066
  author: post.creator,
7961
8067
  categories: post.categories,
7962
8068
  tags: post.tags,
@@ -7967,6 +8073,31 @@ function wxrPostToNormalizedItem(post, attachmentMap) {
7967
8073
  customTaxonomies
7968
8074
  };
7969
8075
  }
8076
+ /**
8077
+ * WordPress uses "0000-00-00 00:00:00" as a sentinel for missing GMT dates
8078
+ * (e.g. unpublished drafts). This must be treated as absent.
8079
+ */
8080
+ const WXR_ZERO_DATE = "0000-00-00 00:00:00";
8081
+ /**
8082
+ * Parse a WXR date with the correct fallback chain:
8083
+ * 1. GMT date (always UTC, most reliable)
8084
+ * 2. pubDate (RFC 2822, includes timezone offset)
8085
+ * 3. Site-local date (MySQL datetime without timezone, imprecise but best available)
8086
+ *
8087
+ * Returns undefined when none of the inputs yield a valid date.
8088
+ * Callers that need a guaranteed Date should use `?? new Date()`.
8089
+ */
8090
+ function parseWxrDate(gmtDate, pubDate, localDate) {
8091
+ if (gmtDate && gmtDate !== WXR_ZERO_DATE) return /* @__PURE__ */ new Date(gmtDate.replace(" ", "T") + "Z");
8092
+ if (pubDate) {
8093
+ const d = new Date(pubDate);
8094
+ if (!isNaN(d.getTime())) return d;
8095
+ }
8096
+ if (localDate) {
8097
+ const d = new Date(localDate.replace(" ", "T"));
8098
+ if (!isNaN(d.getTime())) return d;
8099
+ }
8100
+ }
7970
8101
 
7971
8102
  //#endregion
7972
8103
  //#region src/import/sources/wordpress-rest.ts
@@ -8606,7 +8737,7 @@ async function resolveMenuItem(item, db, urlPatterns) {
8606
8737
  return {
8607
8738
  id: item.id,
8608
8739
  label: item.label,
8609
- url,
8740
+ url: sanitizeHref(url),
8610
8741
  target: item.target || void 0,
8611
8742
  titleAttr: item.title_attr || void 0,
8612
8743
  cssClasses: item.css_classes || void 0,
@@ -8632,6 +8763,7 @@ function interpolateUrlPattern(pattern, slug, id) {
8632
8763
  async function resolveContentUrl(collection, entryId, db, urlPatterns) {
8633
8764
  if (!entryId) return null;
8634
8765
  try {
8766
+ validateIdentifier(collection, "menu item collection");
8635
8767
  const row = (await sql`
8636
8768
  SELECT slug FROM ${sql.ref(`ec_${collection}`)} WHERE id = ${entryId} LIMIT 1
8637
8769
  `.execute(db)).rows[0];
@@ -8806,7 +8938,7 @@ async function getTermsForEntries(collection, entryIds, taxonomyName) {
8806
8938
  * Get entries by term (wraps getEmDashCollection)
8807
8939
  */
8808
8940
  async function getEntriesByTerm(collection, taxonomyName, termSlug) {
8809
- const { getEmDashCollection } = await import("./query-BVYN0PJ6.mjs").then((n) => n.a);
8941
+ const { getEmDashCollection } = await import("./query-sesiOndV.mjs").then((n) => n.a);
8810
8942
  const { entries } = await getEmDashCollection(collection, { where: { [taxonomyName]: termSlug } });
8811
8943
  return entries;
8812
8944
  }
@@ -9314,5 +9446,5 @@ function extractSearchableFields(entry, fields) {
9314
9446
  }
9315
9447
 
9316
9448
  //#endregion
9317
- export { definePlugin as $, getFileSources as A, handleContentGetIncludingTrashed as At, PluginManager as B, handleContentUpdate as Bt, isPreviewRequest as C, handleContentCountScheduled as Ct, wxrSource as D, handleContentDiscardDraft as Dt, wordpressRestSource as E, handleContentDelete as Et, importReusableBlocksAsSections as F, handleContentRestore as Ft, devConsoleEmailDeliver as G, PluginRouteError as H, portableText as Ht, isStandardPluginDefinition as I, handleContentSchedule as It, createHookPipeline as J, EmailPipeline as K, NoopSandboxRunner as L, handleContentTranslations as Lt, getUrlSources as M, handleContentListTrashed as Mt, probeUrl as N, handleContentPermanentDelete as Nt, clearSources as O, handleContentDuplicate as Ot, registerSource as P, handleContentPublish as Pt, sanitizeHeadersForSandbox as Q, SandboxNotAvailableError as R, handleContentUnpublish as Rt, getPreviewToken as S, handleContentCompare as St, getPreviewUrl as T, handleContentCreate as Tt, PluginRouteRegistry as U, reference as Ut, createPluginManager as V, validateRev as Vt, DEV_CONSOLE_EMAIL_PLUGIN_ID as W, image as Wt, CronExecutor as X, resolveExclusiveHooks as Y, extractRequestMeta as Z, getTermsForEntries as _, handleRevisionList as _t, search as a, prosemirrorToPortableText as at, getCommentCount as b, computeContentHash as bt, getWidgetArea as c, getSections as ct, getEntriesByTerm as d, handleMediaCreate as dt, parseWxr as et, getEntryTerms as f, handleMediaDelete as ft, getTerm as g, handleRevisionGet as gt, getTaxonomyTerms as h, handleMediaUpdate as ht, getSuggestions as i, portableTextToProsemirror as it, getSource as j, handleContentList as jt, getAllSources as k, handleContentGet as kt, getWidgetAreas as l, PluginStateRepository as lt, getTaxonomyDefs as m, handleMediaList as mt, extractSearchableFields as n, isSafeHref as nt, searchCollection as o, loadBundleFromR2 as ot, getTaxonomyDef as p, handleMediaGet as pt, HookPipeline as q, getSearchStats as r, sanitizeHref as rt, searchWithDb as s, getSection as st, extractPlainText as t, parseWxrString as tt, getWidgetComponents as u, getCollectionInfo as ut, getMenu as v, handleRevisionRestore as vt, buildPreviewUrl as w, handleContentCountTrashed as wt, getComments as x, hashString as xt, getMenus as y, generateManifest as yt, createNoopSandboxRunner as z, handleContentUnschedule as zt };
9318
- //# sourceMappingURL=search-C1gg67nN.mjs.map
9449
+ export { sanitizeHeadersForSandbox as $, getAllSources as A, handleContentGet as At, createNoopSandboxRunner as B, handleContentUnschedule as Bt, isPreviewRequest as C, handleContentCompare as Ct, parseWxrDate as D, handleContentDelete as Dt, wordpressRestSource as E, handleContentCreate as Et, registerSource as F, handleContentPublish as Ft, DEV_CONSOLE_EMAIL_PLUGIN_ID as G, image as Gt, createPluginManager as H, validateRev as Ht, importReusableBlocksAsSections as I, handleContentRestore as It, HookPipeline as J, devConsoleEmailDeliver as K, isStandardPluginDefinition as L, handleContentSchedule as Lt, getSource as M, handleContentList as Mt, getUrlSources as N, handleContentListTrashed as Nt, wxrSource as O, handleContentDiscardDraft as Ot, probeUrl as P, handleContentPermanentDelete as Pt, extractRequestMeta as Q, NoopSandboxRunner as R, handleContentTranslations as Rt, getPreviewToken as S, hashString as St, getPreviewUrl as T, handleContentCountTrashed as Tt, PluginRouteError as U, portableText as Ut, PluginManager as V, handleContentUpdate as Vt, PluginRouteRegistry as W, reference as Wt, resolveExclusiveHooks as X, createHookPipeline as Y, CronExecutor as Z, getTermsForEntries as _, handleRevisionGet as _t, search as a, isSafeHref as at, getCommentCount as b, generateManifest as bt, getWidgetArea as c, getSection as ct, getEntriesByTerm as d, getCollectionInfo as dt, definePlugin as et, getEntryTerms as f, handleMediaCreate as ft, getTerm as g, handleMediaUpdate as gt, getTaxonomyTerms as h, handleMediaList as ht, getSuggestions as i, prosemirrorToPortableText as it, getFileSources as j, handleContentGetIncludingTrashed as jt, clearSources as k, handleContentDuplicate as kt, getWidgetAreas as l, getSections as lt, getTaxonomyDefs as m, handleMediaGet as mt, extractSearchableFields as n, parseWxrString as nt, searchCollection as o, sanitizeHref as ot, getTaxonomyDef as p, handleMediaDelete as pt, EmailPipeline as q, getSearchStats as r, portableTextToProsemirror as rt, searchWithDb as s, loadBundleFromR2 as st, extractPlainText as t, parseWxr as tt, getWidgetComponents as u, PluginStateRepository as ut, getMenu as v, handleRevisionList as vt, buildPreviewUrl as w, handleContentCountScheduled as wt, getComments as x, computeContentHash as xt, getMenus as y, handleRevisionRestore as yt, SandboxNotAvailableError as z, handleContentUnpublish as zt };
9450
+ //# sourceMappingURL=search-BXB-jfu2.mjs.map