diff --git a/apps/cms/src/app/(frontend)/[locale]/preview/global-block/[id]/page.tsx b/apps/cms/src/app/(frontend)/[locale]/preview/global-block/[id]/page.tsx
new file mode 100644
index 000000000..3889593dc
--- /dev/null
+++ b/apps/cms/src/app/(frontend)/[locale]/preview/global-block/[id]/page.tsx
@@ -0,0 +1,32 @@
+import type { Metadata } from "next";
+import { notFound } from "next/navigation";
+
+import { GlobalBlockRenderer } from "@/blocks/GlobalSectionSlot/GlobalBlockRenderer";
+import type { Locale } from "@/lib/types";
+
+export const metadata: Metadata = {
+ robots: { follow: false, index: false },
+ title: "Global Block Preview",
+};
+
+interface Args {
+ params: Promise<{
+ id: string;
+ locale: Locale;
+ }>;
+}
+
+export default async function GlobalBlockPreviewPage({ params }: Args) {
+ const { id, locale } = await params;
+ const referenceId = Number(id);
+
+ if (!Number.isInteger(referenceId)) {
+ notFound();
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/apps/cms/src/blocks/GlobalSectionSlot/Component.tsx b/apps/cms/src/blocks/GlobalSectionSlot/Component.tsx
index 265e1b869..94ef91d77 100644
--- a/apps/cms/src/blocks/GlobalSectionSlot/Component.tsx
+++ b/apps/cms/src/blocks/GlobalSectionSlot/Component.tsx
@@ -1,17 +1,10 @@
-import { draftMode } from "next/headers";
-
-import { renderContentBlock } from "@/blocks/contentBlockComponents";
-import { filterHiddenBlocks } from "@/lib/fields/section/filterHiddenBlocks";
import type { GlobalSectionSlotBlock } from "@/payload-types";
-export const GlobalSectionSlotBlockComponent = async ({ reference }: GlobalSectionSlotBlock) => {
- if (!reference || typeof reference === "number") return null;
-
- const { isEnabled: draft } = await draftMode();
- if (!draft && reference._status !== "published") return null;
+import { GlobalBlockRenderer } from "./GlobalBlockRenderer";
- const [inner] = filterHiddenBlocks(reference.block);
- if (!inner) return null;
+export const GlobalSectionSlotBlockComponent = ({ reference }: GlobalSectionSlotBlock) => {
+ const referenceId = typeof reference === "object" && reference ? reference.id : reference;
+ if (referenceId == null) return null;
- return renderContentBlock(inner, inner.id ?? "global-section-inner");
+ return ;
};
diff --git a/apps/cms/src/blocks/GlobalSectionSlot/GlobalBlockRenderer.tsx b/apps/cms/src/blocks/GlobalSectionSlot/GlobalBlockRenderer.tsx
new file mode 100644
index 000000000..8673985ea
--- /dev/null
+++ b/apps/cms/src/blocks/GlobalSectionSlot/GlobalBlockRenderer.tsx
@@ -0,0 +1,39 @@
+import { draftMode } from "next/headers";
+
+import { renderContentBlock } from "@/blocks/contentBlockComponents";
+import { isPublishedGlobalBlock } from "@/collections/GlobalBlock/slots";
+import { getPayloadClient, PAGE_RENDER_DEPTH } from "@/dal";
+import { filterHiddenBlocks } from "@/lib/fields/section/filterHiddenBlocks";
+import type { Locale } from "@/lib/types";
+import { resolveLocale } from "@/lib/utils/resolveLocale";
+
+// Re-fetched because the page query spends one depth level on the slot, and inner blocks need the full depth.
+export const GlobalBlockRenderer = async ({
+ locale,
+ referenceId,
+}: {
+ locale?: Locale;
+ referenceId: number;
+}) => {
+ const { isEnabled: draft } = await draftMode();
+ const resolvedLocale = await resolveLocale(locale);
+ const payload = await getPayloadClient();
+
+ const globalBlock = await payload.findByID({
+ collection: "globalBlock",
+ depth: PAGE_RENDER_DEPTH,
+ disableErrors: true,
+ draft,
+ id: referenceId,
+ locale: resolvedLocale,
+ overrideAccess: true,
+ });
+
+ if (!globalBlock) return null;
+ if (!draft && !isPublishedGlobalBlock(globalBlock)) return null;
+
+ const [inner] = filterHiddenBlocks(globalBlock.block);
+ if (!inner) return null;
+
+ return renderContentBlock(inner, inner.id ?? "global-section-inner");
+};
diff --git a/apps/cms/src/blocks/RawHtml/extractText.ts b/apps/cms/src/blocks/RawHtml/extractText.ts
new file mode 100644
index 000000000..ecd188bb5
--- /dev/null
+++ b/apps/cms/src/blocks/RawHtml/extractText.ts
@@ -0,0 +1,35 @@
+import type { RawHtmlBlock } from "@/payload-types";
+
+const TAG_BODY = String.raw`(?:"[^"]*"|'[^']*'|[^>"'])*`;
+const SCRIPT_OR_STYLE = new RegExp(
+ String.raw`<(script|style)\b${TAG_BODY}>[\s\S]*?<\/\1\s*>`,
+ "giu"
+);
+const HTML_COMMENT = //gu;
+const HTML_TAG = new RegExp(`<[a-zA-Z/!?]${TAG_BODY}>`, "gu");
+const NAMED_ENTITIES: Record = {
+ "&": "&",
+ ">": ">",
+ "<": "<",
+ " ": " ",
+ """: '"',
+ "'": "'",
+ "'": "'",
+};
+
+function decodeEntities(input: string): string {
+ return input.replace(
+ /&(?:amp|gt|lt|nbsp|quot|apos|#39);/gu,
+ (match) => NAMED_ENTITIES[match] ?? match
+ );
+}
+
+export function stripHtml(html: string): string {
+ const withoutCode = html.replace(SCRIPT_OR_STYLE, " ").replace(HTML_COMMENT, " ");
+ const withoutTags = withoutCode.replace(HTML_TAG, " ");
+ return decodeEntities(withoutTags).replace(/\s+/gu, " ").trim();
+}
+
+export function extractRawHtmlText(block: RawHtmlBlock): string {
+ return stripHtml(block.html ?? "");
+}
diff --git a/apps/cms/src/collections/GlobalBlock/config.ts b/apps/cms/src/collections/GlobalBlock/config.ts
index 1e2e2ad18..bcbd40559 100644
--- a/apps/cms/src/collections/GlobalBlock/config.ts
+++ b/apps/cms/src/collections/GlobalBlock/config.ts
@@ -3,8 +3,21 @@ import type { CollectionConfig } from "payload";
import { contentBlocks } from "@/blocks/contentBlocks";
import { anyone, author, or, superAdmin, user } from "@/lib/access";
import { createLocalizedDefault } from "@/lib/utils/createLocalizedDefault";
+import { generatePreviewPath } from "@/lib/utils/generatePreviewPath";
+import { shouldIncludeLocalePrefix } from "@/lib/utils/localePrefix";
import { preventDeleteIfReferenced } from "./hooks/preventDeleteIfReferenced";
+import { revalidatePagesUsingGlobalBlock } from "./hooks/revalidatePagesUsingGlobalBlock";
+
+const globalBlockPreviewPath = (id: unknown, locale: string): string => {
+ const prefix = shouldIncludeLocalePrefix(locale) ? `/${locale}` : "";
+
+ return generatePreviewPath({
+ collection: "globalBlock",
+ path: `${prefix}/preview/global-block/${id}`,
+ slug: String(id ?? ""),
+ });
+};
export const GlobalBlock: CollectionConfig<"globalBlock"> = {
access: {
@@ -16,6 +29,11 @@ export const GlobalBlock: CollectionConfig<"globalBlock"> = {
admin: {
defaultColumns: ["title", "block", "updatedAt"],
group: "Global Components",
+ livePreview: {
+ url: ({ data, locale }) =>
+ globalBlockPreviewPath(data?.id, locale.code ?? locale.fallbackLocale),
+ },
+ preview: (data, { locale }) => globalBlockPreviewPath(data?.id, locale),
useAsTitle: "title",
},
dbName: "gsec",
@@ -54,6 +72,7 @@ export const GlobalBlock: CollectionConfig<"globalBlock"> = {
},
],
hooks: {
+ afterChange: [revalidatePagesUsingGlobalBlock],
beforeDelete: [preventDeleteIfReferenced],
},
labels: {
diff --git a/apps/cms/src/collections/GlobalBlock/findPagesUsingGlobalBlock.ts b/apps/cms/src/collections/GlobalBlock/findPagesUsingGlobalBlock.ts
new file mode 100644
index 000000000..bbc084481
--- /dev/null
+++ b/apps/cms/src/collections/GlobalBlock/findPagesUsingGlobalBlock.ts
@@ -0,0 +1,44 @@
+import type { PayloadRequest } from "payload";
+
+import { I18N_CONFIG } from "@/lib/config/i18n";
+
+export interface PageUsingGlobalBlock {
+ id: number;
+ title: string;
+}
+
+export async function findPagesUsingGlobalBlock({
+ id,
+ includeDrafts = false,
+ req,
+}: {
+ id: number | string;
+ includeDrafts?: boolean;
+ req: PayloadRequest;
+}): Promise {
+ const pages = new Map();
+
+ for (const { code: locale } of I18N_CONFIG.locales) {
+ for (const draft of includeDrafts ? [false, true] : [false]) {
+ const { docs } = await req.payload.find({
+ collection: "page",
+ depth: 0,
+ draft,
+ locale,
+ overrideAccess: true,
+ pagination: false,
+ req,
+ select: { title: true },
+ where: { "blocks.reference": { equals: id } },
+ });
+
+ for (const doc of docs) {
+ if (!pages.has(doc.id)) {
+ pages.set(doc.id, { id: doc.id, title: doc.title });
+ }
+ }
+ }
+ }
+
+ return [...pages.values()];
+}
diff --git a/apps/cms/src/collections/GlobalBlock/hooks/preventDeleteIfReferenced.ts b/apps/cms/src/collections/GlobalBlock/hooks/preventDeleteIfReferenced.ts
index a3ba5dfc4..c60181117 100644
--- a/apps/cms/src/collections/GlobalBlock/hooks/preventDeleteIfReferenced.ts
+++ b/apps/cms/src/collections/GlobalBlock/hooks/preventDeleteIfReferenced.ts
@@ -1,26 +1,16 @@
import { APIError } from "payload";
import type { CollectionBeforeDeleteHook } from "payload";
+import { findPagesUsingGlobalBlock } from "../findPagesUsingGlobalBlock";
+
export const preventDeleteIfReferenced: CollectionBeforeDeleteHook = async ({ id, req }) => {
- const pages = await req.payload.find({
- collection: "page",
- depth: 0,
- limit: 100,
- overrideAccess: true,
- pagination: false,
- req,
- where: {
- "blocks.reference": { equals: id },
- },
- });
+ const pages = await findPagesUsingGlobalBlock({ id, includeDrafts: true, req });
- if (pages.totalDocs > 0) {
- const titles = pages.docs
- .map((p) => (typeof p.title === "string" ? p.title : `#${p.id}`))
- .join(", ");
+ if (pages.length > 0) {
+ const titles = pages.map((p) => p.title || `#${p.id}`).join(", ");
throw new APIError(
- `Cannot delete this global block: it is still used by ${pages.totalDocs} page(s): ${titles}. Remove those references first.`,
+ `Cannot delete this global block: it is still used by ${pages.length} page(s): ${titles}. Remove those references first.`,
400,
undefined,
true
diff --git a/apps/cms/src/collections/GlobalBlock/hooks/revalidatePagesUsingGlobalBlock.ts b/apps/cms/src/collections/GlobalBlock/hooks/revalidatePagesUsingGlobalBlock.ts
new file mode 100644
index 000000000..dc3c9a23c
--- /dev/null
+++ b/apps/cms/src/collections/GlobalBlock/hooks/revalidatePagesUsingGlobalBlock.ts
@@ -0,0 +1,68 @@
+import type { CollectionAfterChangeHook, PayloadRequest } from "payload";
+
+import { I18N_CONFIG } from "@/lib/config/i18n";
+import { revalidatePageCache } from "@/lib/utils/revalidatePageCache";
+import type { GlobalBlock } from "@/payload-types";
+
+import { findPagesUsingGlobalBlock } from "../findPagesUsingGlobalBlock";
+import { isPublishedGlobalBlock } from "../slots";
+
+async function changesPublishedContent({
+ doc,
+ previousDoc,
+ req,
+}: {
+ doc: GlobalBlock;
+ previousDoc?: GlobalBlock;
+ req: PayloadRequest;
+}): Promise {
+ if (isPublishedGlobalBlock(doc)) return true;
+ if (!isPublishedGlobalBlock(previousDoc)) return false;
+
+ const live = await req.payload.findByID({
+ collection: "globalBlock",
+ depth: 0,
+ id: doc.id,
+ overrideAccess: true,
+ req,
+ select: { _status: true },
+ });
+
+ return !isPublishedGlobalBlock(live);
+}
+
+export const revalidatePagesUsingGlobalBlock: CollectionAfterChangeHook = async ({
+ doc,
+ previousDoc,
+ req,
+}) => {
+ const { context, payload } = req;
+
+ if (context.disableRevalidate || !(await changesPublishedContent({ doc, previousDoc, req }))) {
+ return doc;
+ }
+
+ const pageIds = (await findPagesUsingGlobalBlock({ id: doc.id, req })).map((p) => p.id);
+ if (pageIds.length === 0) {
+ return doc;
+ }
+
+ for (const { code: locale } of I18N_CONFIG.locales) {
+ const { docs: publishedPages } = await payload.find({
+ collection: "page",
+ depth: 0,
+ locale,
+ overrideAccess: true,
+ pagination: false,
+ req,
+ select: { breadcrumbs: true, slug: true },
+ where: { _status: { equals: "published" }, id: { in: pageIds } },
+ });
+
+ for (const page of publishedPages) {
+ revalidatePageCache({ doc: page, locale, payload });
+ }
+ }
+
+ return doc;
+};
diff --git a/apps/cms/src/collections/GlobalBlock/slots.ts b/apps/cms/src/collections/GlobalBlock/slots.ts
new file mode 100644
index 000000000..496ded718
--- /dev/null
+++ b/apps/cms/src/collections/GlobalBlock/slots.ts
@@ -0,0 +1,21 @@
+import { relationId } from "@/lib/contentExtraction";
+import type { GlobalBlock } from "@/payload-types";
+
+interface BlockLike {
+ blockType: string;
+ reference?: unknown;
+}
+
+export function collectGlobalBlockIds(blocks: readonly BlockLike[]): (string | number)[] {
+ const ids = new Set();
+
+ for (const block of blocks) {
+ const id = block.blockType === "globalSectionSlot" ? relationId(block.reference) : null;
+ if (id != null) ids.add(id);
+ }
+
+ return [...ids];
+}
+
+export const isPublishedGlobalBlock = (doc: Pick | null | undefined) =>
+ doc?._status === "published";
diff --git a/apps/cms/src/collections/Page/extractPageContent.ts b/apps/cms/src/collections/Page/extractPageContent.ts
index 8a3a879f4..9d8562017 100644
--- a/apps/cms/src/collections/Page/extractPageContent.ts
+++ b/apps/cms/src/collections/Page/extractPageContent.ts
@@ -6,6 +6,7 @@ import type {
DocStore,
} from "@focus-reactive/payload-plugin-seo/content";
+import { collectGlobalBlockIds, isPublishedGlobalBlock } from "@/collections/GlobalBlock/slots";
import { I18N_CONFIG } from "@/lib/config/i18n";
import {
actionLinks,
@@ -21,7 +22,6 @@ import type { ImageGroup, LinkResolveCtx, LinkValue, Upload } from "@/lib/conten
import type { GlobalBlock, Page } from "@/payload-types";
type Block = Page["blocks"][number];
-type GlobalBlockContent = NonNullable[number];
export function extractPageBlockContent(
block: Block,
@@ -153,9 +153,9 @@ export function extractPageBlockContent(
const gid = relationId(b.reference);
const resolved =
gid != null
- ? (docs.get("globalBlock", gid) as { block?: GlobalBlockContent[] } | undefined)
+ ? (docs.get("globalBlock", gid) as Pick | undefined)
: undefined;
- const inner = resolved?.block?.[0];
+ const inner = isPublishedGlobalBlock(resolved) ? resolved?.block?.[0] : undefined;
return inner ? extractPageBlockContent(inner as unknown as Block, ctx, docs, helpers) : [];
}
@@ -164,19 +164,6 @@ export function extractPageBlockContent(
}
}
-function collectGlobalBlockIds(blocks: Block[]): (string | number)[] {
- const ids = new Set();
-
- for (const block of blocks) {
- if (block.blockType === "globalSectionSlot") {
- const id = relationId((block as { reference?: unknown }).reference);
- if (id != null) ids.add(id);
- }
- }
-
- return [...ids];
-}
-
function mergeStores(...stores: (DocStore | null)[]): DocStore {
return {
get: (collection, id) => {
@@ -199,16 +186,15 @@ const extractPageContent: ContentExtractor = async (values, ctx, { resolveDocs,
phase1Queries.push({
collection: "globalBlock",
ids: globalIds,
- select: ["block"],
+ select: ["block", "_status"],
depth: 0,
});
}
const docs1: DocStore = await resolveDocs(phase1Queries);
- const globalDocs = globalIds.map((id) => docs1.get("globalBlock", id)).filter(Boolean) as Record<
- string,
- unknown
- >[];
+ const globalDocs = globalIds
+ .map((id) => docs1.get("globalBlock", id) as Pick | undefined)
+ .filter(isPublishedGlobalBlock) as Record[];
const phase2Queries: DocQuery[] = globalDocs.flatMap((doc) => buildRefQueries(doc));
const docs2: DocStore | null = phase2Queries.length > 0 ? await resolveDocs(phase2Queries) : null;
diff --git a/apps/cms/src/collections/Page/extractPageText.ts b/apps/cms/src/collections/Page/extractPageText.ts
index 37448ee25..33b7024c2 100644
--- a/apps/cms/src/collections/Page/extractPageText.ts
+++ b/apps/cms/src/collections/Page/extractPageText.ts
@@ -3,6 +3,7 @@ import { extractCarouselText } from "@/blocks/Carousel/extractText";
import { extractChartText } from "@/blocks/Chart/extractText";
import { extractCtaBandText } from "@/blocks/CtaBand/extractText";
import { extractNewsletterText } from "@/blocks/Newsletter/extractText";
+import { extractRawHtmlText } from "@/blocks/RawHtml/extractText";
import { extractStatsText } from "@/blocks/Stats/extractText";
import { extractFaqText } from "@/blocks/Faq/extractText";
import { extractHeroText } from "@/blocks/Hero/extractText";
@@ -51,6 +52,13 @@ export function extractPageBlockText(block: Page["blocks"][number]): string {
case "stats": {
return extractStatsText(block);
}
+ case "rawHtml": {
+ return extractRawHtmlText(block);
+ }
+ case "globalSectionSlot": {
+ // Shared text stays out of page vectors, so one global block edit does not re-embed every page using it.
+ return "";
+ }
default: {
return "";
}
diff --git a/apps/cms/src/collections/Page/hooks/indexEmbedding.ts b/apps/cms/src/collections/Page/hooks/indexEmbedding.ts
index 3e6830724..25e14f959 100644
--- a/apps/cms/src/collections/Page/hooks/indexEmbedding.ts
+++ b/apps/cms/src/collections/Page/hooks/indexEmbedding.ts
@@ -1,29 +1,31 @@
-import type { CollectionAfterChangeHook, CollectionAfterDeleteHook } from "payload";
+import type { CollectionAfterChangeHook, CollectionAfterDeleteHook, PayloadRequest } from "payload";
import type { Pool } from "pg";
import { extractPageText } from "@/collections/Page/extractPageText";
-import { getLocaleFromRequest } from "@/lib/utils/getLocaleFromRequest";
+import { I18N_CONFIG } from "@/lib/config/i18n";
+import type { Locale } from "@/lib/types";
import type { Page } from "@/payload-types";
import { upsertEmbedding, deleteEmbedding } from "@/lib/search/dbOperations";
import { generateEmbedding } from "@/lib/search/generateEmbedding";
-export const indexPageEmbedding: CollectionAfterChangeHook = async ({ doc, req }) => {
- if (doc._status !== "published") {
- return doc;
- }
- if (req.context?.skipEmbedding) {
- return doc;
- }
+const DEPTH_FOR_RELATED_TEXT = 1;
+async function upsertPageEmbedding({
+ locale,
+ page,
+ req,
+}: {
+ locale: Locale;
+ page: Page;
+ req: PayloadRequest;
+}): Promise {
try {
- const locale = getLocaleFromRequest(req);
- const text = extractPageText(doc);
- const embedding = await generateEmbedding(text);
+ const embedding = await generateEmbedding(extractPageText(page));
const { pool } = req.payload.db as unknown as { pool: Pool };
await upsertEmbedding({
collection: "page",
- documentId: String(doc.id),
+ documentId: String(page.id),
embedding,
locale,
pool,
@@ -31,6 +33,44 @@ export const indexPageEmbedding: CollectionAfterChangeHook = async ({ doc,
} catch (error) {
req.payload.logger.error({ error }, "Failed to index page embedding");
}
+}
+
+// Locale fallback lets an edit in one locale change the text of the other locales.
+async function reindexPageInEveryLocale({ id, req }: { id: number; req: PayloadRequest }) {
+ for (const { code: locale } of I18N_CONFIG.locales) {
+ const page = await req.payload.findByID({
+ collection: "page",
+ depth: DEPTH_FOR_RELATED_TEXT,
+ disableErrors: true,
+ id,
+ locale,
+ overrideAccess: true,
+ req,
+ });
+
+ if (page?._status === "published") {
+ await upsertPageEmbedding({ locale, page, req });
+ }
+ }
+}
+
+export const indexPageEmbedding: CollectionAfterChangeHook = async ({
+ doc,
+ operation,
+ req,
+}) => {
+ // nested-docs re-saves every new page as an update (resaveSelfAfterCreate), and that update indexes it.
+ if (operation === "create") {
+ return doc;
+ }
+ if (doc._status !== "published") {
+ return doc;
+ }
+ if (req.context?.skipEmbedding) {
+ return doc;
+ }
+
+ await reindexPageInEveryLocale({ id: doc.id, req });
return doc;
};
diff --git a/apps/cms/src/lib/dal/getPageBySlug.ts b/apps/cms/src/lib/dal/getPageBySlug.ts
index 1d50e010b..f7598c377 100644
--- a/apps/cms/src/lib/dal/getPageBySlug.ts
+++ b/apps/cms/src/lib/dal/getPageBySlug.ts
@@ -10,6 +10,8 @@ import { getPayloadClient } from "@/dal/payload-client";
import { getAllDocuments } from "./getAllDocuments";
+export const PAGE_RENDER_DEPTH = 3;
+
export async function getPageBySlugQuery(
payload: Payload,
pathSegmentsNorm: string[],
@@ -20,7 +22,7 @@ export async function getPageBySlugQuery(
const lastSegment = pathSegmentsNorm.at(-1)!;
const docs = await getAllDocuments(payload, "page", {
- depth: 3,
+ depth: PAGE_RENDER_DEPTH,
draft,
locale: resolvedLocale,
overrideAccess: true,
diff --git a/apps/cms/src/lib/dal/index.ts b/apps/cms/src/lib/dal/index.ts
index 2352a91f3..2b9ccd7fc 100644
--- a/apps/cms/src/lib/dal/index.ts
+++ b/apps/cms/src/lib/dal/index.ts
@@ -8,7 +8,7 @@
export { getPayloadClient } from "./payload-client";
// Pages
-export { getPageBySlug } from "./getPageBySlug";
+export { getPageBySlug, PAGE_RENDER_DEPTH } from "./getPageBySlug";
// Posts
export { getPostBySlug } from "./getPostBySlug";
diff --git a/apps/cms/src/lib/plugins/index.ts b/apps/cms/src/lib/plugins/index.ts
index f63d03e65..a6d5d9594 100644
--- a/apps/cms/src/lib/plugins/index.ts
+++ b/apps/cms/src/lib/plugins/index.ts
@@ -18,6 +18,7 @@ import type { Field, PayloadRequest, Plugin } from "payload";
import { Authors } from "@/collections/Authors";
import { Categories } from "@/collections/Categories";
import { Footer } from "@/collections/Footer/config";
+import { GlobalBlock } from "@/collections/GlobalBlock/config";
import { Header } from "@/collections/Header/config";
import { Page as PageCollection } from "@/collections/Page/Page";
import serverExtractPageContent from "@/collections/Page/serverExtractPageContent";
@@ -299,6 +300,7 @@ export const plugins: Plugin[] = [
{ slug: "testimonials", titleField: "author" },
{ slug: "header", titleField: "name" },
{ slug: "footer", titleField: "name" },
+ { slug: "globalBlock", titleField: "title" },
],
translations: {
@@ -333,7 +335,7 @@ export const plugins: Plugin[] = [
}),
schedulePublicationPlugin({
- collections: ["page", "posts"],
+ collections: ["page", "posts", "globalBlock"],
globals: ["site-settings"],
schedulePublish: {
timeIntervals: 60,
@@ -342,8 +344,17 @@ export const plugins: Plugin[] = [
}),
translatorPlugin({
- collections: [PageCollection, Posts, Categories, Authors, Testimonials, Header, Footer].map(
- (col) => JSON.parse(JSON.stringify(col, (_, v) => (typeof v === "function" ? undefined : v)))
+ collections: [
+ PageCollection,
+ Posts,
+ Categories,
+ Authors,
+ Testimonials,
+ Header,
+ Footer,
+ GlobalBlock,
+ ].map((col) =>
+ JSON.parse(JSON.stringify(col, (_, v) => (typeof v === "function" ? undefined : v)))
),
access: { check: ({ req }) => Boolean(req.user) },
runner: createSyncRunner(),
diff --git a/apps/cms/src/lib/utils/generatePreviewPath.ts b/apps/cms/src/lib/utils/generatePreviewPath.ts
index a0c6a20b2..ee15db173 100644
--- a/apps/cms/src/lib/utils/generatePreviewPath.ts
+++ b/apps/cms/src/lib/utils/generatePreviewPath.ts
@@ -1,7 +1,7 @@
import type { BLOG_CONFIG } from "@/lib/config/blog";
interface Props {
- collection: "page" | typeof BLOG_CONFIG.collection;
+ collection: "page" | "globalBlock" | typeof BLOG_CONFIG.collection;
slug: string;
path: string;
}
diff --git a/apps/cms/src/lib/utils/revalidatePageCache.ts b/apps/cms/src/lib/utils/revalidatePageCache.ts
index 4007fe782..62a095e45 100644
--- a/apps/cms/src/lib/utils/revalidatePageCache.ts
+++ b/apps/cms/src/lib/utils/revalidatePageCache.ts
@@ -7,7 +7,11 @@ import { getPathFromBreadcrumbs } from "../utils/path/getPathFromBreadcrumbs";
import { cacheTag } from "./cacheTags";
import { revalidateScopedTag } from "./scopedCache";
-export function revalidatePageCache(params: { doc: Page; locale: Locale; payload: Payload }): void {
+export function revalidatePageCache(params: {
+ doc: Pick;
+ locale: Locale;
+ payload: Payload;
+}): void {
const path = getPathFromBreadcrumbs(params.doc.breadcrumbs) ?? "home";
params.payload.logger?.info?.(`Revalidating page with slug: ${params.doc.slug}`);
revalidateScopedTag(cacheTag({ locale: params.locale, path, type: "page" }), "max");