emdash 0.1.0 → 0.2.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/LICENSE +9 -0
- package/dist/{adapters-BLMa4JGD.d.mts → adapters-N6BF7RCD.d.mts} +1 -1
- package/dist/{adapters-BLMa4JGD.d.mts.map → adapters-N6BF7RCD.d.mts.map} +1 -1
- package/dist/{apply-Bjfq_b4-.mjs → apply-wmVEOSbR.mjs} +57 -10
- package/dist/apply-wmVEOSbR.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.mjs +90 -22
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +5 -5
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +127 -56
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/request-context.mjs +85 -23
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +107 -43
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +31 -9
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{byline-CL847F26.mjs → byline-1WQPlISL.mjs} +51 -29
- package/dist/byline-1WQPlISL.mjs.map +1 -0
- package/dist/{bylines-C2a-2TGt.mjs → bylines-BYdTYmia.mjs} +10 -8
- package/dist/{bylines-C2a-2TGt.mjs.map → bylines-BYdTYmia.mjs.map} +1 -1
- package/dist/cli/index.mjs +75 -13
- 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-CKE8p9xM.mjs → config-Cq8H0SfX.mjs} +2 -10
- package/dist/{config-CKE8p9xM.mjs.map → config-Cq8H0SfX.mjs.map} +1 -1
- package/dist/{content-D6C2WsZC.mjs → content-BmXndhdi.mjs} +16 -3
- package/dist/content-BmXndhdi.mjs.map +1 -0
- 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/{default-Cyi4aAxu.mjs → default-WYlzADZL.mjs} +1 -1
- package/dist/{default-Cyi4aAxu.mjs.map → default-WYlzADZL.mjs.map} +1 -1
- package/dist/{error-Cxz0tQeO.mjs → error-DrxtnGPg.mjs} +1 -1
- package/dist/{error-Cxz0tQeO.mjs.map → error-DrxtnGPg.mjs.map} +1 -1
- package/dist/{index-C1xF3OGh.d.mts → index-UHEVQMus.d.mts} +83 -14
- package/dist/index-UHEVQMus.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +18 -18
- package/dist/{load-yOOlckBj.mjs → load-Veizk2cT.mjs} +1 -1
- package/dist/{load-yOOlckBj.mjs.map → load-Veizk2cT.mjs.map} +1 -1
- package/dist/{loader-fz8Q_3EO.mjs → loader-CHb2v0jm.mjs} +1 -1
- package/dist/{loader-fz8Q_3EO.mjs.map → loader-CHb2v0jm.mjs.map} +1 -1
- package/dist/{manifest-schema-Dcl0R6nM.mjs → manifest-schema-CuMio1A9.mjs} +5 -2
- package/dist/manifest-schema-CuMio1A9.mjs.map +1 -0
- 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/{mode-C2EzN1uE.mjs → mode-CYeM2rPt.mjs} +1 -1
- package/dist/{mode-C2EzN1uE.mjs.map → mode-CYeM2rPt.mjs.map} +1 -1
- package/dist/page/index.d.mts +10 -1
- package/dist/page/index.d.mts.map +1 -1
- package/dist/page/index.mjs +8 -4
- package/dist/page/index.mjs.map +1 -1
- package/dist/{placeholder-SmpOx-_v.mjs → placeholder-aiCD8aSZ.mjs} +27 -2
- package/dist/placeholder-aiCD8aSZ.mjs.map +1 -0
- package/dist/{placeholder-CmGAmqeO.d.mts → placeholder-bOx1xCTY.d.mts} +10 -2
- package/dist/{placeholder-CmGAmqeO.d.mts.map → placeholder-bOx1xCTY.d.mts.map} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
- package/dist/{query-CS_iSj34.mjs → query-5Hcv_5ER.mjs} +20 -8
- package/dist/{query-CS_iSj34.mjs.map → query-5Hcv_5ER.mjs.map} +1 -1
- package/dist/{registry-D_w5HW4G.mjs → registry-1EvbAfsC.mjs} +27 -38
- package/dist/registry-1EvbAfsC.mjs.map +1 -0
- package/dist/{runner-C0hCbYnD.mjs → runner-BoN0-FPi.mjs} +276 -158
- package/dist/runner-BoN0-FPi.mjs.map +1 -0
- package/dist/{runner-EAtf0ZIe.d.mts → runner-DTqkzOzc.d.mts} +2 -2
- package/dist/runner-DTqkzOzc.d.mts.map +1 -0
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +1 -1
- package/dist/{search-DG603UrT.mjs → search-BsYMed12.mjs} +355 -118
- package/dist/search-BsYMed12.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +8 -8
- 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/{tokens-DpgrkrXK.mjs → tokens-DrB-W6Q-.mjs} +1 -1
- package/dist/{tokens-DpgrkrXK.mjs.map → tokens-DrB-W6Q-.mjs.map} +1 -1
- package/dist/{transport-yxiQsi8I.mjs → transport-Bl8cTdYt.mjs} +1 -1
- package/dist/{transport-yxiQsi8I.mjs.map → transport-Bl8cTdYt.mjs.map} +1 -1
- package/dist/{transport-BFGblqwG.d.mts → transport-COOs9GSE.d.mts} +1 -1
- package/dist/{transport-BFGblqwG.d.mts.map → transport-COOs9GSE.d.mts.map} +1 -1
- package/dist/{types-DvhsUmSJ.d.mts → types-6dqxBqsH.d.mts} +93 -106
- package/dist/types-6dqxBqsH.d.mts.map +1 -0
- package/dist/{types-DRjfYOEv.d.mts → types-7-UjSEyB.d.mts} +1 -1
- package/dist/{types-DRjfYOEv.d.mts.map → types-7-UjSEyB.d.mts.map} +1 -1
- package/dist/{types-CUBbjgmP.mjs → types-Bec-r_3_.mjs} +1 -1
- package/dist/{types-CUBbjgmP.mjs.map → types-Bec-r_3_.mjs.map} +1 -1
- package/dist/{types-DaNLHo_T.d.mts → types-BljtYPSd.d.mts} +1 -1
- package/dist/{types-DaNLHo_T.d.mts.map → types-BljtYPSd.d.mts.map} +1 -1
- package/dist/{types-BRuPJGdV.d.mts → types-CIsTnQvJ.d.mts} +3 -1
- package/dist/types-CIsTnQvJ.d.mts.map +1 -0
- package/dist/types-CMMN0pNg.mjs.map +1 -1
- package/dist/{types-C4-fAxN3.d.mts → types-CcreFIIH.d.mts} +13 -2
- package/dist/types-CcreFIIH.d.mts.map +1 -0
- package/dist/{types-DY5zk5HN.mjs → types-DuNbGKjF.mjs} +5 -3
- package/dist/types-DuNbGKjF.mjs.map +1 -0
- package/dist/{validate-CpBtVMsD.d.mts → validate-B7KP7VLM.d.mts} +4 -4
- package/dist/{validate-CpBtVMsD.d.mts.map → validate-B7KP7VLM.d.mts.map} +1 -1
- package/dist/{validate-O7PWmlnq.mjs → validate-CXnRKfJK.mjs} +2 -2
- package/dist/{validate-O7PWmlnq.mjs.map → validate-CXnRKfJK.mjs.map} +1 -1
- package/package.json +9 -7
- package/src/api/csrf.ts +13 -2
- package/src/api/handlers/content.ts +7 -0
- package/src/api/handlers/dashboard.ts +4 -8
- package/src/api/handlers/device-flow.ts +55 -37
- package/src/api/handlers/index.ts +6 -1
- package/src/api/handlers/marketplace.ts +7 -4
- package/src/api/handlers/seo.ts +48 -21
- package/src/api/public-url.ts +84 -0
- package/src/api/schemas/content.ts +2 -2
- package/src/api/schemas/menus.ts +12 -2
- package/src/api/schemas/schema.ts +12 -0
- package/src/astro/integration/index.ts +41 -1
- package/src/astro/integration/routes.ts +13 -2
- package/src/astro/integration/runtime.ts +15 -0
- package/src/astro/integration/virtual-modules.ts +13 -1
- package/src/astro/integration/vite-config.ts +52 -9
- package/src/astro/middleware/auth.ts +60 -56
- package/src/astro/middleware/csp.ts +25 -0
- package/src/astro/middleware.ts +31 -3
- package/src/astro/routes/PluginRegistry.tsx +8 -2
- package/src/astro/routes/admin.astro +8 -3
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +3 -1
- package/src/astro/routes/api/admin/users/[id]/disable.ts +18 -12
- package/src/astro/routes/api/admin/users/[id]/index.ts +26 -5
- package/src/astro/routes/api/auth/invite/complete.ts +3 -1
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +2 -1
- package/src/astro/routes/api/auth/oauth/[provider].ts +2 -1
- package/src/astro/routes/api/auth/passkey/options.ts +3 -1
- package/src/astro/routes/api/auth/passkey/register/options.ts +3 -1
- package/src/astro/routes/api/auth/passkey/register/verify.ts +3 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +3 -1
- package/src/astro/routes/api/auth/signup/complete.ts +3 -1
- package/src/astro/routes/api/content/[collection]/index.ts +31 -3
- package/src/astro/routes/api/import/wordpress/analyze.ts +24 -3
- package/src/astro/routes/api/import/wordpress/execute.ts +14 -1
- package/src/astro/routes/api/import/wordpress/prepare.ts +2 -2
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +10 -0
- package/src/astro/routes/api/manifest.ts +1 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +7 -2
- package/src/astro/routes/api/media.ts +16 -4
- package/src/astro/routes/api/oauth/authorize.ts +12 -7
- package/src/astro/routes/api/oauth/device/code.ts +5 -1
- package/src/astro/routes/api/search/index.ts +1 -5
- package/src/astro/routes/api/search/suggest.ts +1 -5
- package/src/astro/routes/api/setup/admin-verify.ts +3 -1
- package/src/astro/routes/api/setup/admin.ts +3 -1
- package/src/astro/routes/api/setup/dev-bypass.ts +2 -1
- package/src/astro/routes/api/setup/index.ts +3 -2
- package/src/astro/routes/api/snapshot.ts +2 -1
- package/src/astro/routes/api/themes/preview.ts +2 -1
- package/src/astro/routes/api/well-known/auth.ts +1 -0
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +3 -2
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +3 -2
- package/src/astro/routes/robots.txt.ts +5 -1
- package/src/astro/routes/sitemap-[collection].xml.ts +104 -0
- package/src/astro/routes/sitemap.xml.ts +18 -23
- package/src/astro/types.ts +28 -1
- package/src/auth/passkey-config.ts +20 -3
- package/src/bylines/index.ts +11 -8
- package/src/cli/commands/bundle-utils.ts +26 -0
- package/src/cli/commands/bundle.ts +15 -0
- package/src/cli/commands/content.ts +11 -1
- package/src/cli/commands/login.ts +7 -2
- package/src/cli/commands/media.ts +5 -1
- package/src/cli/commands/menu.ts +3 -1
- package/src/cli/commands/schema.ts +7 -1
- package/src/cli/commands/search-cmd.ts +2 -1
- package/src/cli/commands/taxonomy.ts +4 -1
- package/src/cli/output.ts +14 -0
- package/src/components/InlinePortableTextEditor.tsx +38 -6
- package/src/content/converters/portable-text-to-prosemirror.ts +50 -2
- package/src/database/migrations/033_optimize_content_indexes.ts +113 -0
- package/src/database/migrations/034_published_at_index.ts +29 -0
- package/src/database/migrations/runner.ts +42 -33
- package/src/database/repositories/byline.ts +48 -42
- package/src/database/repositories/comment.ts +32 -20
- package/src/database/repositories/content.ts +23 -1
- package/src/database/repositories/options.ts +9 -3
- package/src/database/repositories/seo.ts +34 -17
- package/src/database/repositories/types.ts +2 -0
- package/src/emdash-runtime.ts +125 -20
- package/src/import/index.ts +1 -1
- package/src/import/sources/wxr.ts +45 -2
- package/src/index.ts +9 -1
- package/src/mcp/server.ts +85 -5
- package/src/media/placeholder.ts +31 -0
- package/src/media/thumbnail.ts +32 -0
- package/src/menus/index.ts +2 -1
- package/src/page/context.ts +13 -1
- package/src/page/jsonld.ts +10 -6
- package/src/page/seo-contributions.ts +1 -1
- package/src/plugins/context.ts +145 -35
- package/src/plugins/hooks.ts +91 -0
- package/src/plugins/manager.ts +34 -0
- package/src/plugins/manifest-schema.ts +3 -0
- package/src/plugins/marketplace.ts +25 -12
- package/src/plugins/types.ts +104 -4
- package/src/query.ts +18 -0
- package/src/schema/registry.ts +26 -25
- package/src/schema/types.ts +27 -1
- package/src/search/fts-manager.ts +1 -18
- package/src/settings/index.ts +64 -0
- package/src/utils/chunks.ts +17 -0
- package/src/visual-editing/toolbar.ts +84 -22
- package/dist/apply-Bjfq_b4-.mjs.map +0 -1
- package/dist/byline-CL847F26.mjs.map +0 -1
- package/dist/content-D6C2WsZC.mjs.map +0 -1
- package/dist/index-C1xF3OGh.d.mts.map +0 -1
- package/dist/manifest-schema-Dcl0R6nM.mjs.map +0 -1
- package/dist/placeholder-SmpOx-_v.mjs.map +0 -1
- package/dist/registry-D_w5HW4G.mjs.map +0 -1
- package/dist/runner-C0hCbYnD.mjs.map +0 -1
- package/dist/runner-EAtf0ZIe.d.mts.map +0 -1
- package/dist/search-DG603UrT.mjs.map +0 -1
- package/dist/types-BRuPJGdV.d.mts.map +0 -1
- package/dist/types-C4-fAxN3.d.mts.map +0 -1
- package/dist/types-DY5zk5HN.mjs.map +0 -1
- package/dist/types-DvhsUmSJ.d.mts.map +0 -1
- /package/src/astro/routes/api/media/file/{[key].ts → [...key].ts} +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { o as jsonExtractExpr } from "./dialect-helpers-B9uSp2GJ.mjs";
|
|
2
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-
|
|
3
|
+
import { i as slugify, r as RevisionRepository, t as ContentRepository } from "./content-BmXndhdi.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
|
|
8
|
-
import { a as withTransaction, i as FTSManager, n as SchemaRegistry } from "./registry-
|
|
7
|
+
import { a as stripCredentialHeaders, f as OptionsRepository, i as ssrfSafeFetch, o as validateExternalUrl, r as SsrfError } from "./apply-wmVEOSbR.mjs";
|
|
8
|
+
import { a as withTransaction, i as FTSManager, n as SchemaRegistry } from "./registry-1EvbAfsC.mjs";
|
|
9
9
|
import { t as RedirectRepository } from "./redirect-DIfIni3r.mjs";
|
|
10
|
-
import { t as BylineRepository } from "./byline-
|
|
11
|
-
import {
|
|
12
|
-
import { n as getDb } from "./loader-
|
|
13
|
-
import { i as pluginManifestSchema } from "./manifest-schema-
|
|
14
|
-
import { t as generatePreviewToken } from "./tokens-
|
|
10
|
+
import { n as SQL_BATCH_SIZE, r as chunks, t as BylineRepository } from "./byline-1WQPlISL.mjs";
|
|
11
|
+
import { r as isI18nEnabled } from "./config-Cq8H0SfX.mjs";
|
|
12
|
+
import { n as getDb } from "./loader-CHb2v0jm.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";
|
|
@@ -323,20 +323,24 @@ var CommentRepository = class CommentRepository {
|
|
|
323
323
|
}
|
|
324
324
|
/**
|
|
325
325
|
* Count comments grouped by status (for inbox badges)
|
|
326
|
+
*
|
|
327
|
+
* Uses four parallel COUNT queries with WHERE filters to leverage partial indexes
|
|
328
|
+
* (idx_comments_pending, idx_comments_approved, idx_comments_spam, idx_comments_trash)
|
|
329
|
+
* instead of a full table GROUP BY scan.
|
|
326
330
|
*/
|
|
327
331
|
async countByStatus() {
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
332
|
+
const [pending, approved, spam, trash] = await Promise.all([
|
|
333
|
+
this.db.selectFrom("_emdash_comments").select((eb) => eb.fn.count("id").as("count")).where("status", "=", "pending").executeTakeFirst(),
|
|
334
|
+
this.db.selectFrom("_emdash_comments").select((eb) => eb.fn.count("id").as("count")).where("status", "=", "approved").executeTakeFirst(),
|
|
335
|
+
this.db.selectFrom("_emdash_comments").select((eb) => eb.fn.count("id").as("count")).where("status", "=", "spam").executeTakeFirst(),
|
|
336
|
+
this.db.selectFrom("_emdash_comments").select((eb) => eb.fn.count("id").as("count")).where("status", "=", "trash").executeTakeFirst()
|
|
337
|
+
]);
|
|
338
|
+
return {
|
|
339
|
+
pending: Number(pending?.count ?? 0),
|
|
340
|
+
approved: Number(approved?.count ?? 0),
|
|
341
|
+
spam: Number(spam?.count ?? 0),
|
|
342
|
+
trash: Number(trash?.count ?? 0)
|
|
334
343
|
};
|
|
335
|
-
for (const row of rows) {
|
|
336
|
-
const status = row.status;
|
|
337
|
-
if (status in counts) counts[status] = Number(row.count);
|
|
338
|
-
}
|
|
339
|
-
return counts;
|
|
340
344
|
}
|
|
341
345
|
/**
|
|
342
346
|
* Count approved comments from a given email address.
|
|
@@ -823,6 +827,13 @@ var SeoRepository = class {
|
|
|
823
827
|
this.db = db;
|
|
824
828
|
}
|
|
825
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
|
+
/**
|
|
826
837
|
* Get SEO data for a content item. Returns null defaults if no row exists.
|
|
827
838
|
*/
|
|
828
839
|
async get(collection, contentId) {
|
|
@@ -837,24 +848,27 @@ var SeoRepository = class {
|
|
|
837
848
|
};
|
|
838
849
|
}
|
|
839
850
|
/**
|
|
840
|
-
* Get SEO data for multiple content items
|
|
851
|
+
* Get SEO data for multiple content items.
|
|
841
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.
|
|
842
857
|
*/
|
|
843
858
|
async getMany(collection, contentIds) {
|
|
844
859
|
const result = /* @__PURE__ */ new Map();
|
|
845
860
|
if (contentIds.length === 0) return result;
|
|
846
|
-
const
|
|
847
|
-
const
|
|
848
|
-
for (const
|
|
849
|
-
const
|
|
850
|
-
|
|
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, {
|
|
851
866
|
title: row.seo_title ?? null,
|
|
852
867
|
description: row.seo_description ?? null,
|
|
853
868
|
image: row.seo_image ?? null,
|
|
854
869
|
canonical: row.seo_canonical ?? null,
|
|
855
870
|
noIndex: row.seo_no_index === 1
|
|
856
871
|
});
|
|
857
|
-
else result.set(id, { ...SEO_DEFAULTS$1 });
|
|
858
872
|
}
|
|
859
873
|
return result;
|
|
860
874
|
}
|
|
@@ -1216,7 +1230,9 @@ async function handleContentCreate(db, collection, body) {
|
|
|
1216
1230
|
status: body.status || "draft",
|
|
1217
1231
|
authorId: body.authorId,
|
|
1218
1232
|
locale: body.locale,
|
|
1219
|
-
translationOf: body.translationOf
|
|
1233
|
+
translationOf: body.translationOf,
|
|
1234
|
+
createdAt: body.createdAt,
|
|
1235
|
+
publishedAt: body.publishedAt
|
|
1220
1236
|
});
|
|
1221
1237
|
if (body.bylines !== void 0) {
|
|
1222
1238
|
await bylineRepo.setContentBylines(collection, created.id, body.bylines);
|
|
@@ -1450,6 +1466,7 @@ async function handleContentPermanentDelete(db, collection, id) {
|
|
|
1450
1466
|
if (wasDeleted) {
|
|
1451
1467
|
await new SeoRepository(trx).delete(collection, resolvedId);
|
|
1452
1468
|
await new CommentRepository(trx).deleteByContent(collection, resolvedId);
|
|
1469
|
+
await new RevisionRepository(trx).deleteByEntry(collection, resolvedId);
|
|
1453
1470
|
}
|
|
1454
1471
|
return wasDeleted;
|
|
1455
1472
|
})) return {
|
|
@@ -2625,7 +2642,7 @@ const contentListQuery = cursorPaginationQuery.extend({
|
|
|
2625
2642
|
const contentCreateBody = z$1.object({
|
|
2626
2643
|
data: z$1.record(z$1.string(), z$1.unknown()),
|
|
2627
2644
|
slug: z$1.string().nullish(),
|
|
2628
|
-
status: z$1.
|
|
2645
|
+
status: z$1.enum(["draft"]).optional(),
|
|
2629
2646
|
bylines: z$1.array(contentBylineInputSchema).optional(),
|
|
2630
2647
|
locale: localeCode.optional(),
|
|
2631
2648
|
translationOf: z$1.string().optional(),
|
|
@@ -2634,7 +2651,7 @@ const contentCreateBody = z$1.object({
|
|
|
2634
2651
|
const contentUpdateBody = z$1.object({
|
|
2635
2652
|
data: z$1.record(z$1.string(), z$1.unknown()).optional(),
|
|
2636
2653
|
slug: z$1.string().nullish(),
|
|
2637
|
-
status: z$1.
|
|
2654
|
+
status: z$1.enum(["draft"]).optional(),
|
|
2638
2655
|
authorId: z$1.string().nullish(),
|
|
2639
2656
|
bylines: z$1.array(contentBylineInputSchema).optional(),
|
|
2640
2657
|
_rev: z$1.string().optional().meta({ description: "Opaque revision token for optimistic concurrency" }),
|
|
@@ -2821,8 +2838,24 @@ const fieldTypeValues = z$1.enum([
|
|
|
2821
2838
|
"file",
|
|
2822
2839
|
"reference",
|
|
2823
2840
|
"json",
|
|
2824
|
-
"slug"
|
|
2841
|
+
"slug",
|
|
2842
|
+
"repeater"
|
|
2825
2843
|
]);
|
|
2844
|
+
const repeaterSubFieldSchema = z$1.object({
|
|
2845
|
+
slug: z$1.string().min(1).max(63).regex(slugPattern, "Invalid slug format"),
|
|
2846
|
+
type: z$1.enum([
|
|
2847
|
+
"string",
|
|
2848
|
+
"text",
|
|
2849
|
+
"number",
|
|
2850
|
+
"integer",
|
|
2851
|
+
"boolean",
|
|
2852
|
+
"datetime",
|
|
2853
|
+
"select"
|
|
2854
|
+
]),
|
|
2855
|
+
label: z$1.string().min(1),
|
|
2856
|
+
required: z$1.boolean().optional(),
|
|
2857
|
+
options: z$1.array(z$1.string()).optional()
|
|
2858
|
+
});
|
|
2826
2859
|
const fieldValidation = z$1.object({
|
|
2827
2860
|
required: z$1.boolean().optional(),
|
|
2828
2861
|
min: z$1.number().optional(),
|
|
@@ -2830,7 +2863,10 @@ const fieldValidation = z$1.object({
|
|
|
2830
2863
|
minLength: z$1.number().int().min(0).optional(),
|
|
2831
2864
|
maxLength: z$1.number().int().min(0).optional(),
|
|
2832
2865
|
pattern: z$1.string().optional(),
|
|
2833
|
-
options: z$1.array(z$1.string()).optional()
|
|
2866
|
+
options: z$1.array(z$1.string()).optional(),
|
|
2867
|
+
subFields: z$1.array(repeaterSubFieldSchema).min(1).optional(),
|
|
2868
|
+
minItems: z$1.number().int().min(0).optional(),
|
|
2869
|
+
maxItems: z$1.number().int().min(1).optional()
|
|
2834
2870
|
}).optional();
|
|
2835
2871
|
const fieldWidgetOptions = z$1.record(z$1.string(), z$1.unknown()).optional();
|
|
2836
2872
|
const createCollectionBody = z$1.object({
|
|
@@ -3088,9 +3124,58 @@ const passkeyRegisterVerifyBody = z$1.object({
|
|
|
3088
3124
|
const passkeyRenameBody = z$1.object({ name: z$1.string().min(1) }).meta({ id: "PasskeyRenameBody" });
|
|
3089
3125
|
const authMeActionBody = z$1.object({ action: z$1.string().min(1) }).meta({ id: "AuthMeActionBody" });
|
|
3090
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
|
+
|
|
3091
3175
|
//#endregion
|
|
3092
3176
|
//#region src/api/schemas/menus.ts
|
|
3093
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");
|
|
3094
3179
|
const createMenuBody = z$1.object({
|
|
3095
3180
|
name: z$1.string().min(1),
|
|
3096
3181
|
label: z$1.string().min(1)
|
|
@@ -3101,7 +3186,7 @@ const createMenuItemBody = z$1.object({
|
|
|
3101
3186
|
label: z$1.string().min(1),
|
|
3102
3187
|
referenceCollection: z$1.string().optional(),
|
|
3103
3188
|
referenceId: z$1.string().optional(),
|
|
3104
|
-
customUrl:
|
|
3189
|
+
customUrl: safeHref.optional(),
|
|
3105
3190
|
target: z$1.string().optional(),
|
|
3106
3191
|
titleAttr: z$1.string().optional(),
|
|
3107
3192
|
cssClasses: z$1.string().optional(),
|
|
@@ -3110,7 +3195,7 @@ const createMenuItemBody = z$1.object({
|
|
|
3110
3195
|
}).meta({ id: "CreateMenuItemBody" });
|
|
3111
3196
|
const updateMenuItemBody = z$1.object({
|
|
3112
3197
|
label: z$1.string().min(1).optional(),
|
|
3113
|
-
customUrl:
|
|
3198
|
+
customUrl: safeHref.optional(),
|
|
3114
3199
|
target: z$1.string().optional(),
|
|
3115
3200
|
titleAttr: z$1.string().optional(),
|
|
3116
3201
|
cssClasses: z$1.string().optional(),
|
|
@@ -3895,10 +3980,13 @@ function isTextBlock(block) {
|
|
|
3895
3980
|
return block._type === "block";
|
|
3896
3981
|
}
|
|
3897
3982
|
/**
|
|
3898
|
-
* Type guard for image blocks
|
|
3983
|
+
* Type guard for image blocks.
|
|
3984
|
+
* Checks both `_type` and that `asset` is a valid object — image blocks
|
|
3985
|
+
* without an `asset` wrapper (e.g. `{ _type: "image", url: "..." }`) are
|
|
3986
|
+
* malformed and should not be cast to `PortableTextImageBlock`.
|
|
3899
3987
|
*/
|
|
3900
3988
|
function isImageBlock(block) {
|
|
3901
|
-
return block._type === "image";
|
|
3989
|
+
return block._type === "image" && "asset" in block && typeof block.asset === "object" && block.asset !== null;
|
|
3902
3990
|
}
|
|
3903
3991
|
/**
|
|
3904
3992
|
* Type guard for code blocks
|
|
@@ -3912,6 +4000,7 @@ function isCodeBlock(block) {
|
|
|
3912
4000
|
function convertBlock(block) {
|
|
3913
4001
|
if (isTextBlock(block)) return convertTextBlock(block);
|
|
3914
4002
|
if (isImageBlock(block)) return convertImage(block);
|
|
4003
|
+
if (block._type === "image") return convertMalformedImage(block);
|
|
3915
4004
|
if (isCodeBlock(block)) return convertCodeBlock(block);
|
|
3916
4005
|
if (block._type === "break") return { type: "horizontalRule" };
|
|
3917
4006
|
return {
|
|
@@ -4096,6 +4185,27 @@ function convertImage(block) {
|
|
|
4096
4185
|
};
|
|
4097
4186
|
}
|
|
4098
4187
|
/**
|
|
4188
|
+
* Convert a malformed image block (missing `asset` wrapper) to ProseMirror.
|
|
4189
|
+
* Handles blocks like `{ _type: "image", url: "...", alt: "..." }` that may
|
|
4190
|
+
* originate from migrations or third-party imports.
|
|
4191
|
+
*/
|
|
4192
|
+
function convertMalformedImage(block) {
|
|
4193
|
+
return {
|
|
4194
|
+
type: "image",
|
|
4195
|
+
attrs: {
|
|
4196
|
+
src: "url" in block && typeof block.url === "string" ? block.url : "",
|
|
4197
|
+
alt: "alt" in block && typeof block.alt === "string" ? block.alt : "",
|
|
4198
|
+
title: "caption" in block && typeof block.caption === "string" ? block.caption : "",
|
|
4199
|
+
mediaId: void 0,
|
|
4200
|
+
provider: void 0,
|
|
4201
|
+
width: "width" in block && typeof block.width === "number" ? block.width : void 0,
|
|
4202
|
+
height: "height" in block && typeof block.height === "number" ? block.height : void 0,
|
|
4203
|
+
displayWidth: "displayWidth" in block && typeof block.displayWidth === "number" ? block.displayWidth : void 0,
|
|
4204
|
+
displayHeight: "displayHeight" in block && typeof block.displayHeight === "number" ? block.displayHeight : void 0
|
|
4205
|
+
}
|
|
4206
|
+
};
|
|
4207
|
+
}
|
|
4208
|
+
/**
|
|
4099
4209
|
* Convert code block to ProseMirror
|
|
4100
4210
|
*/
|
|
4101
4211
|
function convertCodeBlock(block) {
|
|
@@ -4109,54 +4219,6 @@ function convertCodeBlock(block) {
|
|
|
4109
4219
|
};
|
|
4110
4220
|
}
|
|
4111
4221
|
|
|
4112
|
-
//#endregion
|
|
4113
|
-
//#region src/utils/url.ts
|
|
4114
|
-
/**
|
|
4115
|
-
* URL scheme validation utilities
|
|
4116
|
-
*
|
|
4117
|
-
* Prevents XSS via dangerous URL schemes (javascript:, data:, vbscript:, etc.)
|
|
4118
|
-
* by allowlisting known-safe schemes before rendering into href attributes.
|
|
4119
|
-
*/
|
|
4120
|
-
/**
|
|
4121
|
-
* Matches URLs that are safe to render in href attributes.
|
|
4122
|
-
*
|
|
4123
|
-
* Allowed:
|
|
4124
|
-
* - http:// and https://
|
|
4125
|
-
* - mailto: and tel:
|
|
4126
|
-
* - Relative paths (starting with /)
|
|
4127
|
-
* - Fragment links (starting with #)
|
|
4128
|
-
* - Protocol-relative URLs are NOT allowed (starting with //) as they can
|
|
4129
|
-
* redirect to attacker-controlled hosts.
|
|
4130
|
-
*/
|
|
4131
|
-
const SAFE_URL_SCHEME_RE = /^(https?:|mailto:|tel:|\/(?!\/)|#)/i;
|
|
4132
|
-
/**
|
|
4133
|
-
* Returns the URL unchanged if it uses a safe scheme, otherwise returns "#".
|
|
4134
|
-
*
|
|
4135
|
-
* Use this at the render layer as the primary defense against XSS via
|
|
4136
|
-
* dangerous URL schemes like `javascript:`, `data:`, or `vbscript:`.
|
|
4137
|
-
*
|
|
4138
|
-
* @example
|
|
4139
|
-
* ```ts
|
|
4140
|
-
* sanitizeHref("https://example.com") // "https://example.com"
|
|
4141
|
-
* sanitizeHref("/about") // "/about"
|
|
4142
|
-
* sanitizeHref("#section") // "#section"
|
|
4143
|
-
* sanitizeHref("mailto:a@b.com") // "mailto:a@b.com"
|
|
4144
|
-
* sanitizeHref("javascript:alert(1)") // "#"
|
|
4145
|
-
* sanitizeHref("data:text/html,<script>") // "#"
|
|
4146
|
-
* sanitizeHref("") // "#"
|
|
4147
|
-
* ```
|
|
4148
|
-
*/
|
|
4149
|
-
function sanitizeHref(url) {
|
|
4150
|
-
if (!url) return "#";
|
|
4151
|
-
return SAFE_URL_SCHEME_RE.test(url) ? url : "#";
|
|
4152
|
-
}
|
|
4153
|
-
/**
|
|
4154
|
-
* Returns true if the URL uses a safe scheme for rendering in href attributes.
|
|
4155
|
-
*/
|
|
4156
|
-
function isSafeHref(url) {
|
|
4157
|
-
return SAFE_URL_SCHEME_RE.test(url);
|
|
4158
|
-
}
|
|
4159
|
-
|
|
4160
4222
|
//#endregion
|
|
4161
4223
|
//#region src/cli/wxr/parser.ts
|
|
4162
4224
|
const PHP_SERIALIZED_STRING_PATTERN = /s:\d+:"([^"]+)"/g;
|
|
@@ -5304,21 +5366,45 @@ function createStorageAccess(db, pluginId, storageConfig) {
|
|
|
5304
5366
|
return storage;
|
|
5305
5367
|
}
|
|
5306
5368
|
/**
|
|
5369
|
+
* Extract `seo` from a plugin-supplied content write input and return both
|
|
5370
|
+
* parts. Mutates nothing — returns a new field map without the `seo` key.
|
|
5371
|
+
*/
|
|
5372
|
+
function splitSeoFromInput(input) {
|
|
5373
|
+
const { seo, ...fields } = input;
|
|
5374
|
+
if (seo !== void 0 && (seo === null || typeof seo !== "object" || Array.isArray(seo))) throw new Error("content.seo must be an object");
|
|
5375
|
+
return {
|
|
5376
|
+
fields,
|
|
5377
|
+
seo
|
|
5378
|
+
};
|
|
5379
|
+
}
|
|
5380
|
+
/**
|
|
5381
|
+
* Reject writing SEO to a collection that does not have it enabled.
|
|
5382
|
+
* Matches the REST API behavior (VALIDATION_ERROR).
|
|
5383
|
+
*/
|
|
5384
|
+
async function assertSeoEnabled(seoRepo, collection, seo) {
|
|
5385
|
+
const hasSeo = await seoRepo.isEnabled(collection);
|
|
5386
|
+
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.`);
|
|
5387
|
+
return hasSeo;
|
|
5388
|
+
}
|
|
5389
|
+
/**
|
|
5307
5390
|
* Create read-only content access
|
|
5308
5391
|
*/
|
|
5309
5392
|
function createContentAccess(db) {
|
|
5310
5393
|
const contentRepo = new ContentRepository(db);
|
|
5394
|
+
const seoRepo = new SeoRepository(db);
|
|
5311
5395
|
return {
|
|
5312
5396
|
async get(collection, id) {
|
|
5313
5397
|
const item = await contentRepo.findById(collection, id);
|
|
5314
5398
|
if (!item) return null;
|
|
5315
|
-
|
|
5399
|
+
const result = {
|
|
5316
5400
|
id: item.id,
|
|
5317
5401
|
type: item.type,
|
|
5318
5402
|
data: item.data,
|
|
5319
5403
|
createdAt: item.createdAt,
|
|
5320
5404
|
updatedAt: item.updatedAt
|
|
5321
5405
|
};
|
|
5406
|
+
if (await seoRepo.isEnabled(collection)) result.seo = await seoRepo.get(collection, item.id);
|
|
5407
|
+
return result;
|
|
5322
5408
|
},
|
|
5323
5409
|
async list(collection, options) {
|
|
5324
5410
|
let orderBy;
|
|
@@ -5334,14 +5420,22 @@ function createContentAccess(db) {
|
|
|
5334
5420
|
cursor: options?.cursor,
|
|
5335
5421
|
orderBy
|
|
5336
5422
|
});
|
|
5423
|
+
const items = result.items.map((item) => ({
|
|
5424
|
+
id: item.id,
|
|
5425
|
+
type: item.type,
|
|
5426
|
+
data: item.data,
|
|
5427
|
+
createdAt: item.createdAt,
|
|
5428
|
+
updatedAt: item.updatedAt
|
|
5429
|
+
}));
|
|
5430
|
+
if (items.length > 0 && await seoRepo.isEnabled(collection)) {
|
|
5431
|
+
const seoMap = await seoRepo.getMany(collection, items.map((i) => i.id));
|
|
5432
|
+
for (const item of items) {
|
|
5433
|
+
const seo = seoMap.get(item.id);
|
|
5434
|
+
if (seo) item.seo = seo;
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5337
5437
|
return {
|
|
5338
|
-
items
|
|
5339
|
-
id: item.id,
|
|
5340
|
-
type: item.type,
|
|
5341
|
-
data: item.data,
|
|
5342
|
-
createdAt: item.createdAt,
|
|
5343
|
-
updatedAt: item.updatedAt
|
|
5344
|
-
})),
|
|
5438
|
+
items,
|
|
5345
5439
|
cursor: result.nextCursor,
|
|
5346
5440
|
hasMore: !!result.nextCursor
|
|
5347
5441
|
};
|
|
@@ -5349,37 +5443,62 @@ function createContentAccess(db) {
|
|
|
5349
5443
|
};
|
|
5350
5444
|
}
|
|
5351
5445
|
/**
|
|
5352
|
-
* Create full content access with write operations
|
|
5446
|
+
* Create full content access with write operations.
|
|
5447
|
+
*
|
|
5448
|
+
* `create` and `update` accept a reserved `seo` key in their `data`
|
|
5449
|
+
* argument. When present, it is routed to the core SEO panel
|
|
5450
|
+
* (`_emdash_seo`) via `SeoRepository.upsert`, in the same transaction as
|
|
5451
|
+
* the content write. The returned `ContentItem.seo` reflects the resulting
|
|
5452
|
+
* SEO state for SEO-enabled collections.
|
|
5353
5453
|
*/
|
|
5354
5454
|
function createContentAccessWithWrite(db) {
|
|
5355
|
-
const contentRepo = new ContentRepository(db);
|
|
5356
5455
|
return {
|
|
5357
5456
|
...createContentAccess(db),
|
|
5358
5457
|
async create(collection, data) {
|
|
5359
|
-
const
|
|
5360
|
-
|
|
5361
|
-
|
|
5458
|
+
const { fields, seo } = splitSeoFromInput(data);
|
|
5459
|
+
return withTransaction(db, async (trx) => {
|
|
5460
|
+
const trxContentRepo = new ContentRepository(trx);
|
|
5461
|
+
const trxSeoRepo = new SeoRepository(trx);
|
|
5462
|
+
const hasSeo = await assertSeoEnabled(trxSeoRepo, collection, seo);
|
|
5463
|
+
const item = await trxContentRepo.create({
|
|
5464
|
+
type: collection,
|
|
5465
|
+
data: fields
|
|
5466
|
+
});
|
|
5467
|
+
const result = {
|
|
5468
|
+
id: item.id,
|
|
5469
|
+
type: item.type,
|
|
5470
|
+
data: item.data,
|
|
5471
|
+
createdAt: item.createdAt,
|
|
5472
|
+
updatedAt: item.updatedAt
|
|
5473
|
+
};
|
|
5474
|
+
if (hasSeo) result.seo = seo !== void 0 ? await trxSeoRepo.upsert(collection, item.id, seo) : await trxSeoRepo.get(collection, item.id);
|
|
5475
|
+
return result;
|
|
5362
5476
|
});
|
|
5363
|
-
return {
|
|
5364
|
-
id: item.id,
|
|
5365
|
-
type: item.type,
|
|
5366
|
-
data: item.data,
|
|
5367
|
-
createdAt: item.createdAt,
|
|
5368
|
-
updatedAt: item.updatedAt
|
|
5369
|
-
};
|
|
5370
5477
|
},
|
|
5371
5478
|
async update(collection, id, data) {
|
|
5372
|
-
const
|
|
5373
|
-
return {
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5479
|
+
const { fields, seo } = splitSeoFromInput(data);
|
|
5480
|
+
return withTransaction(db, async (trx) => {
|
|
5481
|
+
const trxContentRepo = new ContentRepository(trx);
|
|
5482
|
+
const trxSeoRepo = new SeoRepository(trx);
|
|
5483
|
+
const hasSeo = await assertSeoEnabled(trxSeoRepo, collection, seo);
|
|
5484
|
+
const item = Object.keys(fields).length > 0 ? await trxContentRepo.update(collection, id, { data: fields }) : await (async () => {
|
|
5485
|
+
const existing = await trxContentRepo.findById(collection, id);
|
|
5486
|
+
if (!existing) throw new Error("Content not found");
|
|
5487
|
+
return existing;
|
|
5488
|
+
})();
|
|
5489
|
+
const result = {
|
|
5490
|
+
id: item.id,
|
|
5491
|
+
type: item.type,
|
|
5492
|
+
data: item.data,
|
|
5493
|
+
createdAt: item.createdAt,
|
|
5494
|
+
updatedAt: item.updatedAt
|
|
5495
|
+
};
|
|
5496
|
+
if (hasSeo) result.seo = seo !== void 0 ? await trxSeoRepo.upsert(collection, item.id, seo) : await trxSeoRepo.get(collection, item.id);
|
|
5497
|
+
return result;
|
|
5498
|
+
});
|
|
5380
5499
|
},
|
|
5381
5500
|
async delete(collection, id) {
|
|
5382
|
-
return
|
|
5501
|
+
return new ContentRepository(db).delete(collection, id);
|
|
5383
5502
|
}
|
|
5384
5503
|
};
|
|
5385
5504
|
}
|
|
@@ -5471,6 +5590,7 @@ function createMediaAccessWithWrite(db, getUploadUrlFn, storage) {
|
|
|
5471
5590
|
const MAX_PLUGIN_REDIRECTS = 5;
|
|
5472
5591
|
function isHostAllowed(host, allowedHosts) {
|
|
5473
5592
|
return allowedHosts.some((pattern) => {
|
|
5593
|
+
if (pattern === "*") return true;
|
|
5474
5594
|
if (pattern.startsWith("*.")) {
|
|
5475
5595
|
const suffix = pattern.slice(1);
|
|
5476
5596
|
return host.endsWith(suffix) || host === pattern.slice(2);
|
|
@@ -5792,6 +5912,8 @@ var HookPipeline = class HookPipeline {
|
|
|
5792
5912
|
this.registerPluginHook(plugin, "content:afterSave");
|
|
5793
5913
|
this.registerPluginHook(plugin, "content:beforeDelete");
|
|
5794
5914
|
this.registerPluginHook(plugin, "content:afterDelete");
|
|
5915
|
+
this.registerPluginHook(plugin, "content:afterPublish");
|
|
5916
|
+
this.registerPluginHook(plugin, "content:afterUnpublish");
|
|
5795
5917
|
this.registerPluginHook(plugin, "media:beforeUpload");
|
|
5796
5918
|
this.registerPluginHook(plugin, "media:afterUpload");
|
|
5797
5919
|
this.registerPluginHook(plugin, "cron");
|
|
@@ -5822,6 +5944,8 @@ var HookPipeline = class HookPipeline {
|
|
|
5822
5944
|
["content:afterSave", "read:content"],
|
|
5823
5945
|
["content:beforeDelete", "read:content"],
|
|
5824
5946
|
["content:afterDelete", "read:content"],
|
|
5947
|
+
["content:afterPublish", "read:content"],
|
|
5948
|
+
["content:afterUnpublish", "read:content"],
|
|
5825
5949
|
["media:beforeUpload", "write:media"],
|
|
5826
5950
|
["media:afterUpload", "read:media"],
|
|
5827
5951
|
["comment:beforeCreate", "read:users"],
|
|
@@ -6100,6 +6224,72 @@ var HookPipeline = class HookPipeline {
|
|
|
6100
6224
|
return results;
|
|
6101
6225
|
}
|
|
6102
6226
|
/**
|
|
6227
|
+
* Run content:afterPublish hooks (fire-and-forget).
|
|
6228
|
+
*/
|
|
6229
|
+
async runContentAfterPublish(content, collection) {
|
|
6230
|
+
const hooks = this.getTypedHooks("content:afterPublish");
|
|
6231
|
+
const results = [];
|
|
6232
|
+
for (const hook of hooks) {
|
|
6233
|
+
const { handler } = hook;
|
|
6234
|
+
const event = {
|
|
6235
|
+
content,
|
|
6236
|
+
collection
|
|
6237
|
+
};
|
|
6238
|
+
const ctx = this.getContext(hook.pluginId);
|
|
6239
|
+
const start = Date.now();
|
|
6240
|
+
try {
|
|
6241
|
+
await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
|
|
6242
|
+
results.push({
|
|
6243
|
+
success: true,
|
|
6244
|
+
pluginId: hook.pluginId,
|
|
6245
|
+
duration: Date.now() - start
|
|
6246
|
+
});
|
|
6247
|
+
} catch (error) {
|
|
6248
|
+
results.push({
|
|
6249
|
+
success: false,
|
|
6250
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
6251
|
+
pluginId: hook.pluginId,
|
|
6252
|
+
duration: Date.now() - start
|
|
6253
|
+
});
|
|
6254
|
+
if (hook.errorPolicy === "abort") throw error;
|
|
6255
|
+
}
|
|
6256
|
+
}
|
|
6257
|
+
return results;
|
|
6258
|
+
}
|
|
6259
|
+
/**
|
|
6260
|
+
* Run content:afterUnpublish hooks (fire-and-forget).
|
|
6261
|
+
*/
|
|
6262
|
+
async runContentAfterUnpublish(content, collection) {
|
|
6263
|
+
const hooks = this.getTypedHooks("content:afterUnpublish");
|
|
6264
|
+
const results = [];
|
|
6265
|
+
for (const hook of hooks) {
|
|
6266
|
+
const { handler } = hook;
|
|
6267
|
+
const event = {
|
|
6268
|
+
content,
|
|
6269
|
+
collection
|
|
6270
|
+
};
|
|
6271
|
+
const ctx = this.getContext(hook.pluginId);
|
|
6272
|
+
const start = Date.now();
|
|
6273
|
+
try {
|
|
6274
|
+
await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);
|
|
6275
|
+
results.push({
|
|
6276
|
+
success: true,
|
|
6277
|
+
pluginId: hook.pluginId,
|
|
6278
|
+
duration: Date.now() - start
|
|
6279
|
+
});
|
|
6280
|
+
} catch (error) {
|
|
6281
|
+
results.push({
|
|
6282
|
+
success: false,
|
|
6283
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
6284
|
+
pluginId: hook.pluginId,
|
|
6285
|
+
duration: Date.now() - start
|
|
6286
|
+
});
|
|
6287
|
+
if (hook.errorPolicy === "abort") throw error;
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
return results;
|
|
6291
|
+
}
|
|
6292
|
+
/**
|
|
6103
6293
|
* Run media:beforeUpload hooks
|
|
6104
6294
|
*/
|
|
6105
6295
|
async runMediaBeforeUpload(file) {
|
|
@@ -6942,6 +7132,14 @@ var PluginManager = class {
|
|
|
6942
7132
|
};
|
|
6943
7133
|
}
|
|
6944
7134
|
/**
|
|
7135
|
+
* Set the email pipeline used when creating plugin contexts.
|
|
7136
|
+
* Reinitializes routes/hooks if already initialized so ctx.email is available immediately.
|
|
7137
|
+
*/
|
|
7138
|
+
setEmailPipeline(pipeline) {
|
|
7139
|
+
this.factoryOptions.emailPipeline = pipeline;
|
|
7140
|
+
if (this.initialized) this.reinitialize();
|
|
7141
|
+
}
|
|
7142
|
+
/**
|
|
6945
7143
|
* Register a plugin definition
|
|
6946
7144
|
* This resolves the definition and adds it to the manager, but doesn't install it
|
|
6947
7145
|
*/
|
|
@@ -7064,6 +7262,20 @@ var PluginManager = class {
|
|
|
7064
7262
|
return this.hookPipeline.runContentAfterDelete(id, collection);
|
|
7065
7263
|
}
|
|
7066
7264
|
/**
|
|
7265
|
+
* Run content:afterPublish hooks across all active plugins
|
|
7266
|
+
*/
|
|
7267
|
+
async runContentAfterPublish(content, collection) {
|
|
7268
|
+
this.ensureInitialized();
|
|
7269
|
+
return this.hookPipeline.runContentAfterPublish(content, collection);
|
|
7270
|
+
}
|
|
7271
|
+
/**
|
|
7272
|
+
* Run content:afterUnpublish hooks across all active plugins
|
|
7273
|
+
*/
|
|
7274
|
+
async runContentAfterUnpublish(content, collection) {
|
|
7275
|
+
this.ensureInitialized();
|
|
7276
|
+
return this.hookPipeline.runContentAfterUnpublish(content, collection);
|
|
7277
|
+
}
|
|
7278
|
+
/**
|
|
7067
7279
|
* Run media:beforeUpload hooks across all active plugins
|
|
7068
7280
|
*/
|
|
7069
7281
|
async runMediaBeforeUpload(file) {
|
|
@@ -7848,8 +8060,8 @@ function wxrPostToNormalizedItem(post, attachmentMap) {
|
|
|
7848
8060
|
title: post.title || "Untitled",
|
|
7849
8061
|
content,
|
|
7850
8062
|
excerpt: post.excerpt,
|
|
7851
|
-
date: post.
|
|
7852
|
-
modified: post.
|
|
8063
|
+
date: parseWxrDate(post.postDateGmt, post.pubDate, post.postDate) ?? /* @__PURE__ */ new Date(),
|
|
8064
|
+
modified: parseWxrDate(post.postModifiedGmt, void 0, post.postModified),
|
|
7853
8065
|
author: post.creator,
|
|
7854
8066
|
categories: post.categories,
|
|
7855
8067
|
tags: post.tags,
|
|
@@ -7860,6 +8072,31 @@ function wxrPostToNormalizedItem(post, attachmentMap) {
|
|
|
7860
8072
|
customTaxonomies
|
|
7861
8073
|
};
|
|
7862
8074
|
}
|
|
8075
|
+
/**
|
|
8076
|
+
* WordPress uses "0000-00-00 00:00:00" as a sentinel for missing GMT dates
|
|
8077
|
+
* (e.g. unpublished drafts). This must be treated as absent.
|
|
8078
|
+
*/
|
|
8079
|
+
const WXR_ZERO_DATE = "0000-00-00 00:00:00";
|
|
8080
|
+
/**
|
|
8081
|
+
* Parse a WXR date with the correct fallback chain:
|
|
8082
|
+
* 1. GMT date (always UTC, most reliable)
|
|
8083
|
+
* 2. pubDate (RFC 2822, includes timezone offset)
|
|
8084
|
+
* 3. Site-local date (MySQL datetime without timezone, imprecise but best available)
|
|
8085
|
+
*
|
|
8086
|
+
* Returns undefined when none of the inputs yield a valid date.
|
|
8087
|
+
* Callers that need a guaranteed Date should use `?? new Date()`.
|
|
8088
|
+
*/
|
|
8089
|
+
function parseWxrDate(gmtDate, pubDate, localDate) {
|
|
8090
|
+
if (gmtDate && gmtDate !== WXR_ZERO_DATE) return /* @__PURE__ */ new Date(gmtDate.replace(" ", "T") + "Z");
|
|
8091
|
+
if (pubDate) {
|
|
8092
|
+
const d = new Date(pubDate);
|
|
8093
|
+
if (!isNaN(d.getTime())) return d;
|
|
8094
|
+
}
|
|
8095
|
+
if (localDate) {
|
|
8096
|
+
const d = new Date(localDate.replace(" ", "T"));
|
|
8097
|
+
if (!isNaN(d.getTime())) return d;
|
|
8098
|
+
}
|
|
8099
|
+
}
|
|
7863
8100
|
|
|
7864
8101
|
//#endregion
|
|
7865
8102
|
//#region src/import/sources/wordpress-rest.ts
|
|
@@ -8499,7 +8736,7 @@ async function resolveMenuItem(item, db, urlPatterns) {
|
|
|
8499
8736
|
return {
|
|
8500
8737
|
id: item.id,
|
|
8501
8738
|
label: item.label,
|
|
8502
|
-
url,
|
|
8739
|
+
url: sanitizeHref(url),
|
|
8503
8740
|
target: item.target || void 0,
|
|
8504
8741
|
titleAttr: item.title_attr || void 0,
|
|
8505
8742
|
cssClasses: item.css_classes || void 0,
|
|
@@ -8699,7 +8936,7 @@ async function getTermsForEntries(collection, entryIds, taxonomyName) {
|
|
|
8699
8936
|
* Get entries by term (wraps getEmDashCollection)
|
|
8700
8937
|
*/
|
|
8701
8938
|
async function getEntriesByTerm(collection, taxonomyName, termSlug) {
|
|
8702
|
-
const { getEmDashCollection } = await import("./query-
|
|
8939
|
+
const { getEmDashCollection } = await import("./query-5Hcv_5ER.mjs").then((n) => n.a);
|
|
8703
8940
|
const { entries } = await getEmDashCollection(collection, { where: { [taxonomyName]: termSlug } });
|
|
8704
8941
|
return entries;
|
|
8705
8942
|
}
|
|
@@ -9207,5 +9444,5 @@ function extractSearchableFields(entry, fields) {
|
|
|
9207
9444
|
}
|
|
9208
9445
|
|
|
9209
9446
|
//#endregion
|
|
9210
|
-
export {
|
|
9211
|
-
//# sourceMappingURL=search-
|
|
9447
|
+
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 };
|
|
9448
|
+
//# sourceMappingURL=search-BsYMed12.mjs.map
|