pagepilot-visual-editor 1.0.8 → 1.0.9

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/README.md CHANGED
@@ -935,6 +935,90 @@ import { Reader, renderToStaticMarkup } from "pagepilot-visual-editor";
935
935
  const html = renderToStaticMarkup(myDoc, { rootBlockId: "root" });
936
936
  ```
937
937
 
938
+ ### `renderToStaticHtml(CONFIGURATION, options)`
939
+
940
+ Higher-level renderer that always runs through the **same code path** the editor uses on Save Changes / Publish: bake formFields, sanitize block types this build doesn't know about (e.g. `Mdx` → `Text`), then call `renderToStaticMarkup`. One function, three call shapes — the first is sync, the API-backed shapes return a `Promise<string>`.
941
+
942
+ **1. Sync — render a document you already have in memory**
943
+
944
+ ```ts
945
+ import { renderToStaticHtml } from "pagepilot-visual-editor";
946
+
947
+ const generatedHtml = renderToStaticHtml(CONFIGURATION, {
948
+ rootBlockId: "root",
949
+ });
950
+ ```
951
+
952
+ - `CONFIGURATION` is the document tree (`{ root: { type: "EmailLayout", … }, "block-…": {…} }`) — the same shape stored on `contentMetadata.document`.
953
+ - Any `{{token}}`s in the doc are resolved automatically using `document.root.data.formFields`; unknown block types are coerced to `Text` blocks that carry the original `props.html` / `props.text` / `props.markdown`.
954
+
955
+ **2. Async — fetch a page from the pagepilot API and render every section**
956
+
957
+ ```ts
958
+ const generatedHtml = await renderToStaticHtml(CONFIGURATION, {
959
+ rootBlockId: "root",
960
+ pageId, // required — page `_id`
961
+ baseUrl, // e.g. "https://pagepilot.fabbuilder.com/api"
962
+ tenantId, // required
963
+ authToken, // optional bearer for private pages
964
+ });
965
+ ```
966
+
967
+ - `GET ${baseUrl}/tenant/${tenantId}/page/${pageId}`.
968
+ - Every section's `contentMetadata.document` is baked (with its own `formFields`) and re-rendered — the stored `section.content` HTML is **never reused**.
969
+ - Multi-section pages are wrapped in a `<!doctype html>` shell (viewport meta, body background/font from the first internal section's EmailLayout). Pass `wrapInDocumentShell: false` to get bare fragments instead.
970
+ - `CONFIGURATION` acts as a fallback if the API returns no sections.
971
+
972
+ **3. Async — fetch a template, override `variables`, render**
973
+
974
+ ```ts
975
+ const generatedHtml = await renderToStaticHtml({}, {
976
+ rootBlockId: "root",
977
+ templateId, // required — template `_id`
978
+ baseUrl,
979
+ tenantId, // needed unless `isGlobal: true`
980
+ authToken, // optional (skipped when `isGlobal: true`)
981
+ isGlobal: false, // true → GET /global-template/:templateId (public, no auth)
982
+ variables: { fname: "Ishaan", ctaText: "Book Now" },
983
+ });
984
+ ```
985
+
986
+ - **`isGlobal: false`** (default): `GET ${baseUrl}/tenant/${tenantId}/template/${templateId}` with the bearer token.
987
+ - **`isGlobal: true`**: `GET ${baseUrl}/global-template/${templateId}` — public endpoint, no auth header sent.
988
+ - `variables` accepts two shapes:
989
+ - Flat map — `{ fieldId: value }`. Values are stringified. Ids not on the template are appended as text fields, so `{{fname}}` still substitutes even if the template's authors never declared a `fname` field.
990
+ - Array of formField objects — `[{ id, name, type, value, itemFields?, sourceBlockId?, rowIds? }, …]`. Each entry deep-merges onto the template's existing field with the same `id`, so you can override array/repeatable fields (`fabFaqs`, etc.) end-to-end, not just their scalar `value`.
991
+ - Template envelope shapes handled: flat `configuration.root`, page-envelope `configuration.content.contentMetadata.document`, older `configuration.contentMetadata.document`, and `isIndividualBlock` block templates (a synthetic `EmailLayout` root is generated around `configuration.rootBlockId`).
992
+
993
+ **All options**
994
+
995
+ ```ts
996
+ type RenderToStaticHtmlOptions = {
997
+ rootBlockId?: string; // "root"
998
+ injectElementIds?: boolean; // true — emits `id="ahd-<blockId>"`
999
+ minifyCss?: boolean; // true
1000
+ includePageCSS?: boolean; // true
1001
+ lang?: string; // falls back to document.root.data.lang
1002
+ skipLink?: TSkipLink; // falls back to root.data.skipLink
1003
+ accessibilityTools?: TA11yTools; // falls back to root.data.accessibilityTools
1004
+ injectTabsScrollSyncScript?: boolean; // true when the doc has a Tabs block
1005
+ injectCarouselScript?: boolean; // true when the doc has a Carousel block
1006
+ injectFontAssets?: boolean; // banners only
1007
+ seo?: TSeo;
1008
+ headHtml?: string; // appended before </head>
1009
+ bodyBottomHtml?: string; // appended before </body>
1010
+ isBanner?: boolean;
1011
+ // Page-only:
1012
+ pageId?: string; tenantId?: string; baseUrl?: string; authToken?: string;
1013
+ wrapInDocumentShell?: boolean; // true — set false to get raw fragments
1014
+ // Template-only:
1015
+ templateId?: string; isGlobal?: boolean;
1016
+ variables?: Record<string, string | number | boolean | null | undefined>
1017
+ | Array<Partial<TemplateFormField>>;
1018
+ fetchFn?: typeof fetch; // custom fetch for Node < 18 / tracing
1019
+ };
1020
+ ```
1021
+
938
1022
  ---
939
1023
 
940
1024
  ## Exports
@@ -955,7 +1039,8 @@ export {
955
1039
  // Reader / renderer
956
1040
  export {
957
1041
  Reader, ReaderBlock,
958
- renderToStaticMarkup,
1042
+ renderToStaticMarkup, // low-level React → HTML string
1043
+ renderToStaticHtml, // high-level: bake + sanitize + render, also fetches page / template from the API
959
1044
  ReaderBlockSchema, ReaderDocumentSchema,
960
1045
  } from "pagepilot-visual-editor";
961
1046
 
@@ -972,6 +1057,10 @@ export type {
972
1057
  BlockKey, BlockToggles,
973
1058
  PagepilotApiConfig, ListParams, PageListRow, PageListResponse,
974
1059
  TReaderBlock, TReaderDocument, TReaderBlockProps, TReaderProps,
1060
+ RenderToStaticHtmlOptions,
1061
+ RenderToStaticHtmlSyncOptions,
1062
+ RenderToStaticHtmlPageOptions,
1063
+ RenderToStaticHtmlTemplateOptions,
975
1064
  } from "pagepilot-visual-editor";
976
1065
  ```
977
1066
 
@@ -0,0 +1,68 @@
1
+ export interface SectionForRender {
2
+ sectionType?: "internal" | "external" | string;
3
+ content?: string;
4
+ contentMetadata?: {
5
+ document?: any;
6
+ [key: string]: any;
7
+ };
8
+ [key: string]: any;
9
+ }
10
+ export interface RenderDocumentForSaveOptions {
11
+ rootBlockId?: string;
12
+ injectTabsScrollSyncScript?: boolean;
13
+ injectCarouselScript?: boolean;
14
+ injectFontAssets?: boolean;
15
+ headHtml?: string;
16
+ bodyBottomHtml?: string;
17
+ isBanner?: boolean;
18
+ seo?: any;
19
+ }
20
+ export declare function isExternalSection(section: SectionForRender | undefined): boolean;
21
+ /**
22
+ * Same call `Shell.buildPayload` makes: `renderToStaticMarkup` with the
23
+ * defaults the editor persists on Save Changes (root id, ids injected,
24
+ * page CSS included, css minified, lang / skipLink / accessibilityTools
25
+ * auto-derived from the document's root data).
26
+ */
27
+ export declare function renderDocumentForSave(document: any, options?: RenderDocumentForSaveOptions): string;
28
+ export interface CombineSectionsOptions extends RenderDocumentForSaveOptions {
29
+ /**
30
+ * External sections come from the API as stored `content` strings —
31
+ * usually `<div id="…"></div>` anchor placeholders. When true (default),
32
+ * that content is passed through. When false, external sections are
33
+ * skipped entirely.
34
+ */
35
+ includeExternalSections?: boolean;
36
+ /**
37
+ * When false, only the concatenated fragments are returned — no
38
+ * `<!doctype html>` shell. Defaults to true, matching Shell.buildPayload.
39
+ */
40
+ wrapInDocumentShell?: boolean;
41
+ /**
42
+ * Overrides for the outer shell. When omitted, values come from the
43
+ * first internal section's EmailLayout root data.
44
+ */
45
+ lang?: string;
46
+ canvasColor?: string;
47
+ fontFamily?: string;
48
+ seoTitle?: string;
49
+ seoDescription?: string;
50
+ extraHead?: string;
51
+ extraBodyBottom?: string;
52
+ }
53
+ /**
54
+ * Render each section's document to a fragment, then stitch them into the
55
+ * same `<!doctype html>` shell Shell.tsx uses on Save Changes.
56
+ *
57
+ * Returns `{ html, sectionHtml, flushedSections }`:
58
+ * - `html`: combined document (or bare fragments when `wrapInDocumentShell` is false).
59
+ * - `sectionHtml`: per-section fragments in input order.
60
+ * - `flushedSections`: input sections with their re-rendered `content` field
61
+ * written back — matches the shape `Shell.buildPayload` persists in
62
+ * `SaveChangesPayload.record.sections`.
63
+ */
64
+ export declare function combineSectionsToHtml(sections: SectionForRender[], options?: CombineSectionsOptions): {
65
+ html: string;
66
+ sectionHtml: string[];
67
+ flushedSections: SectionForRender[];
68
+ };
@@ -12,6 +12,31 @@
12
12
  * - Legacy `templateRecord.formFields` mirror at the record root.
13
13
  */
14
14
  export declare function resolveTemplateFormFields(templateRecord: any): any[];
15
+ /**
16
+ * Unwrap a saved template `configuration` down to the raw
17
+ * `{ root, "block-…" }` document tree the renderer / editor expect.
18
+ *
19
+ * The standalone editor is a **page-only** surface, so this helper only
20
+ * knows the page-shaped envelopes. Ordered by frequency, matches the
21
+ * `PageTypes.Page` branch of ahd-fe's `normalizeTemplateConfiguration`
22
+ * plus the block-template synthesis path:
23
+ *
24
+ * 1. `configuration.root` — flat document tree (legacy / tenant templates,
25
+ * most global page templates the backend serves today).
26
+ * 2. `configuration.isIndividualBlock` block template — no root exists;
27
+ * synthesize a minimal EmailLayout that hosts `configuration.rootBlockId`
28
+ * so the standalone renderer has something to walk. `formFields` on
29
+ * the configuration wrapper are hoisted onto the synthetic root's
30
+ * `data.formFields` so `bakeTemplateValues` finds them via the usual
31
+ * `getTemplateForms(document)` path.
32
+ * 3. `configuration.content.contentMetadata.document` — the page envelope
33
+ * the backend uses for page-derived global templates (CyberSecurity Page).
34
+ * 4. `configuration.contentMetadata.document` — one-off older shape.
35
+ * 5. `record.contentMetadata.document` — bare page-record envelope.
36
+ *
37
+ * Returns `null` when no candidate is available so callers can fall back.
38
+ */
39
+ export declare function resolveTemplateDocument(templateRecord: any): any | null;
15
40
  /**
16
41
  * Rewrite container-repeat fields' `sourceBlockId` / `rowIds` through an id
17
42
  * remap. Used by individual-block apply: block ids are regenerated so the