@unlayer/react-elements 0.1.16 → 0.1.17
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 +20 -2
- package/dist/index.cjs +170 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -18
- package/dist/index.d.ts +47 -18
- package/dist/index.js +170 -25
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.d.cts
CHANGED
|
@@ -826,32 +826,54 @@ declare const UnlayerProvider: React__default.FC<UnlayerProviderProps>;
|
|
|
826
826
|
declare function useUnlayerConfig(): UnlayerConfig;
|
|
827
827
|
|
|
828
828
|
/**
|
|
829
|
-
*
|
|
829
|
+
* Options for renderToHtml — config overrides plus document-level extras.
|
|
830
|
+
*/
|
|
831
|
+
interface RenderToHtmlOptions extends Partial<UnlayerConfig> {
|
|
832
|
+
/** Document `<title>` */
|
|
833
|
+
title?: string;
|
|
834
|
+
/**
|
|
835
|
+
* Web-font stylesheets to load via `<link rel="stylesheet">` tags.
|
|
836
|
+
* Elements has no font registry, so pass the URLs for any non-system
|
|
837
|
+
* fonts your design uses (e.g. Google Fonts CSS URLs).
|
|
838
|
+
*/
|
|
839
|
+
fonts?: Array<{
|
|
840
|
+
url: string;
|
|
841
|
+
}>;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Renders an Unlayer element tree to a complete HTML document — from
|
|
845
|
+
* `<!DOCTYPE ...>` to `</html>` — matching the editor's exportHtml layouts.
|
|
830
846
|
*
|
|
831
|
-
* -
|
|
832
|
-
* -
|
|
833
|
-
*
|
|
847
|
+
* Per-mode document shells (mirroring the editor's layout templates):
|
|
848
|
+
* - `email`: XHTML transitional doctype, VML/Office namespaces, and the MSO
|
|
849
|
+
* conditional comments Outlook needs
|
|
850
|
+
* - `web`: HTML5 doctype with standard viewport/charset meta tags
|
|
851
|
+
* - `document`: XHTML transitional doctype for print/PDF pipelines
|
|
834
852
|
*
|
|
835
|
-
* @
|
|
836
|
-
*
|
|
837
|
-
*
|
|
853
|
+
* Use {@link renderToHtmlParts} instead when your app owns the document
|
|
854
|
+
* shell and only needs the head/body chunks.
|
|
855
|
+
*
|
|
856
|
+
* @param element - A React element tree (e.g. `<Email><Row>...</Row></Email>`)
|
|
857
|
+
* @param options - Config overrides (mode, cdnBaseUrl, etc.) plus `title` and `fonts`
|
|
858
|
+
* @returns Complete HTML document string
|
|
838
859
|
* @throws {Error} If rendering fails, with a helpful message
|
|
839
860
|
*
|
|
840
861
|
* @example
|
|
841
862
|
* ```tsx
|
|
842
|
-
* import { renderToHtml,
|
|
863
|
+
* import { renderToHtml, Email, Row, Column, Paragraph } from "@unlayer/react-elements";
|
|
843
864
|
*
|
|
844
865
|
* const html = renderToHtml(
|
|
845
|
-
* <
|
|
846
|
-
* {
|
|
866
|
+
* <Email><Row><Column><Paragraph>Hello</Paragraph></Column></Row></Email>,
|
|
867
|
+
* { title: "Welcome" }
|
|
847
868
|
* );
|
|
869
|
+
* // "<!DOCTYPE HTML PUBLIC ...><html ...>...</html>"
|
|
848
870
|
* ```
|
|
849
871
|
*/
|
|
850
|
-
declare function renderToHtml(element: React__default.ReactElement,
|
|
872
|
+
declare function renderToHtml(element: React__default.ReactElement, options?: RenderToHtmlOptions): string;
|
|
851
873
|
/**
|
|
852
874
|
* Renders an Unlayer element tree to a plain text string.
|
|
853
875
|
*
|
|
854
|
-
*
|
|
876
|
+
* Renders the body markup then converts to plain text using
|
|
855
877
|
* `htmlToPlainText`. Useful for generating the text/plain MIME part
|
|
856
878
|
* of multipart emails (critical for deliverability).
|
|
857
879
|
*
|
|
@@ -876,19 +898,26 @@ declare function renderToPlainText(element: React__default.ReactElement, config?
|
|
|
876
898
|
interface HtmlParts {
|
|
877
899
|
/** `<head>` content: `<style>` blocks with component CSS, optional `<script>` tags */
|
|
878
900
|
head: string;
|
|
879
|
-
/**
|
|
901
|
+
/**
|
|
902
|
+
* The rendered body markup, wrapped in the renderer's host `<div>`. In
|
|
903
|
+
* email/document mode it contains the `<body>` tag itself; web mode has
|
|
904
|
+
* none. No doctype/html/head shell — embeddable as-is, or extract the
|
|
905
|
+
* `<body>` tag when composing a full document.
|
|
906
|
+
*/
|
|
880
907
|
body: string;
|
|
881
908
|
}
|
|
882
909
|
/**
|
|
883
910
|
* Renders an Unlayer element tree to separate head and body HTML strings.
|
|
884
911
|
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
887
|
-
* and
|
|
912
|
+
* Use this when your app owns the document shell — an existing page
|
|
913
|
+
* template, an ESP template with its own meta tags, an iframe srcdoc —
|
|
914
|
+
* and you want to place the `<style>` block and body markup yourself.
|
|
915
|
+
* For a complete ready-to-send document in one call, use
|
|
916
|
+
* {@link renderToHtml}.
|
|
888
917
|
*
|
|
889
918
|
* - `head` contains `<style>` tags with CSS generated by each component's
|
|
890
919
|
* head function (button hover colors, body fonts, link styles, etc.)
|
|
891
|
-
* - `body` is
|
|
920
|
+
* - `body` is the rendered body markup without any document shell
|
|
892
921
|
*
|
|
893
922
|
* @param element - A React element tree (e.g. `<Email><Row>...</Row></Email>`)
|
|
894
923
|
* @param config - Optional config overrides (mode, cdnBaseUrl, etc.)
|
|
@@ -972,4 +1001,4 @@ declare function renderToHtmlParts(element: React__default.ReactElement, config?
|
|
|
972
1001
|
declare function renderRowToJson(element: React__default.ReactElement): DesignRow;
|
|
973
1002
|
declare function renderToJson(element: React__default.ReactElement): DesignJSON;
|
|
974
1003
|
|
|
975
|
-
export { Body, type BorderInput, Button, type ButtonProps, Column, type ColumnLayout, ColumnLayouts, DEFAULT_CONFIG, type DesignBody, type DesignColumn, type DesignContent, type DesignJSON, type DesignRow, Divider, type DividerProps, Document, type DocumentProps, Email, type EmailProps, type FontFamilyInput, type FontWeightInput, Heading, type HeadingLevel, type HeadingProps, type Href, Html, type HtmlParts, type HtmlProps, type Icons, Image, type ImageProps, type ImageSrcInput, type LinkStyle, Menu, type MenuItem, type MenuProps, Page, type PageProps, Paragraph, type ParagraphProps, Row, type RowProps, type SizeInput, Social, type SocialIcon, type SocialProps, Table, type TableProps, type TextAlign, type TextStyleProps, type UnlayerConfig, UnlayerProvider, type UnlayerProviderProps, type ValidColumnLayout, Video, type VideoProps, type VideoSource, htmlToTextJson, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
|
|
1004
|
+
export { Body, type BorderInput, Button, type ButtonProps, Column, type ColumnLayout, ColumnLayouts, DEFAULT_CONFIG, type DesignBody, type DesignColumn, type DesignContent, type DesignJSON, type DesignRow, Divider, type DividerProps, Document, type DocumentProps, Email, type EmailProps, type FontFamilyInput, type FontWeightInput, Heading, type HeadingLevel, type HeadingProps, type Href, Html, type HtmlParts, type HtmlProps, type Icons, Image, type ImageProps, type ImageSrcInput, type LinkStyle, Menu, type MenuItem, type MenuProps, Page, type PageProps, Paragraph, type ParagraphProps, type RenderToHtmlOptions, Row, type RowProps, type SizeInput, Social, type SocialIcon, type SocialProps, Table, type TableProps, type TextAlign, type TextStyleProps, type UnlayerConfig, UnlayerProvider, type UnlayerProviderProps, type ValidColumnLayout, Video, type VideoProps, type VideoSource, htmlToTextJson, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
|
package/dist/index.d.ts
CHANGED
|
@@ -826,32 +826,54 @@ declare const UnlayerProvider: React__default.FC<UnlayerProviderProps>;
|
|
|
826
826
|
declare function useUnlayerConfig(): UnlayerConfig;
|
|
827
827
|
|
|
828
828
|
/**
|
|
829
|
-
*
|
|
829
|
+
* Options for renderToHtml — config overrides plus document-level extras.
|
|
830
|
+
*/
|
|
831
|
+
interface RenderToHtmlOptions extends Partial<UnlayerConfig> {
|
|
832
|
+
/** Document `<title>` */
|
|
833
|
+
title?: string;
|
|
834
|
+
/**
|
|
835
|
+
* Web-font stylesheets to load via `<link rel="stylesheet">` tags.
|
|
836
|
+
* Elements has no font registry, so pass the URLs for any non-system
|
|
837
|
+
* fonts your design uses (e.g. Google Fonts CSS URLs).
|
|
838
|
+
*/
|
|
839
|
+
fonts?: Array<{
|
|
840
|
+
url: string;
|
|
841
|
+
}>;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Renders an Unlayer element tree to a complete HTML document — from
|
|
845
|
+
* `<!DOCTYPE ...>` to `</html>` — matching the editor's exportHtml layouts.
|
|
830
846
|
*
|
|
831
|
-
* -
|
|
832
|
-
* -
|
|
833
|
-
*
|
|
847
|
+
* Per-mode document shells (mirroring the editor's layout templates):
|
|
848
|
+
* - `email`: XHTML transitional doctype, VML/Office namespaces, and the MSO
|
|
849
|
+
* conditional comments Outlook needs
|
|
850
|
+
* - `web`: HTML5 doctype with standard viewport/charset meta tags
|
|
851
|
+
* - `document`: XHTML transitional doctype for print/PDF pipelines
|
|
834
852
|
*
|
|
835
|
-
* @
|
|
836
|
-
*
|
|
837
|
-
*
|
|
853
|
+
* Use {@link renderToHtmlParts} instead when your app owns the document
|
|
854
|
+
* shell and only needs the head/body chunks.
|
|
855
|
+
*
|
|
856
|
+
* @param element - A React element tree (e.g. `<Email><Row>...</Row></Email>`)
|
|
857
|
+
* @param options - Config overrides (mode, cdnBaseUrl, etc.) plus `title` and `fonts`
|
|
858
|
+
* @returns Complete HTML document string
|
|
838
859
|
* @throws {Error} If rendering fails, with a helpful message
|
|
839
860
|
*
|
|
840
861
|
* @example
|
|
841
862
|
* ```tsx
|
|
842
|
-
* import { renderToHtml,
|
|
863
|
+
* import { renderToHtml, Email, Row, Column, Paragraph } from "@unlayer/react-elements";
|
|
843
864
|
*
|
|
844
865
|
* const html = renderToHtml(
|
|
845
|
-
* <
|
|
846
|
-
* {
|
|
866
|
+
* <Email><Row><Column><Paragraph>Hello</Paragraph></Column></Row></Email>,
|
|
867
|
+
* { title: "Welcome" }
|
|
847
868
|
* );
|
|
869
|
+
* // "<!DOCTYPE HTML PUBLIC ...><html ...>...</html>"
|
|
848
870
|
* ```
|
|
849
871
|
*/
|
|
850
|
-
declare function renderToHtml(element: React__default.ReactElement,
|
|
872
|
+
declare function renderToHtml(element: React__default.ReactElement, options?: RenderToHtmlOptions): string;
|
|
851
873
|
/**
|
|
852
874
|
* Renders an Unlayer element tree to a plain text string.
|
|
853
875
|
*
|
|
854
|
-
*
|
|
876
|
+
* Renders the body markup then converts to plain text using
|
|
855
877
|
* `htmlToPlainText`. Useful for generating the text/plain MIME part
|
|
856
878
|
* of multipart emails (critical for deliverability).
|
|
857
879
|
*
|
|
@@ -876,19 +898,26 @@ declare function renderToPlainText(element: React__default.ReactElement, config?
|
|
|
876
898
|
interface HtmlParts {
|
|
877
899
|
/** `<head>` content: `<style>` blocks with component CSS, optional `<script>` tags */
|
|
878
900
|
head: string;
|
|
879
|
-
/**
|
|
901
|
+
/**
|
|
902
|
+
* The rendered body markup, wrapped in the renderer's host `<div>`. In
|
|
903
|
+
* email/document mode it contains the `<body>` tag itself; web mode has
|
|
904
|
+
* none. No doctype/html/head shell — embeddable as-is, or extract the
|
|
905
|
+
* `<body>` tag when composing a full document.
|
|
906
|
+
*/
|
|
880
907
|
body: string;
|
|
881
908
|
}
|
|
882
909
|
/**
|
|
883
910
|
* Renders an Unlayer element tree to separate head and body HTML strings.
|
|
884
911
|
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
887
|
-
* and
|
|
912
|
+
* Use this when your app owns the document shell — an existing page
|
|
913
|
+
* template, an ESP template with its own meta tags, an iframe srcdoc —
|
|
914
|
+
* and you want to place the `<style>` block and body markup yourself.
|
|
915
|
+
* For a complete ready-to-send document in one call, use
|
|
916
|
+
* {@link renderToHtml}.
|
|
888
917
|
*
|
|
889
918
|
* - `head` contains `<style>` tags with CSS generated by each component's
|
|
890
919
|
* head function (button hover colors, body fonts, link styles, etc.)
|
|
891
|
-
* - `body` is
|
|
920
|
+
* - `body` is the rendered body markup without any document shell
|
|
892
921
|
*
|
|
893
922
|
* @param element - A React element tree (e.g. `<Email><Row>...</Row></Email>`)
|
|
894
923
|
* @param config - Optional config overrides (mode, cdnBaseUrl, etc.)
|
|
@@ -972,4 +1001,4 @@ declare function renderToHtmlParts(element: React__default.ReactElement, config?
|
|
|
972
1001
|
declare function renderRowToJson(element: React__default.ReactElement): DesignRow;
|
|
973
1002
|
declare function renderToJson(element: React__default.ReactElement): DesignJSON;
|
|
974
1003
|
|
|
975
|
-
export { Body, type BorderInput, Button, type ButtonProps, Column, type ColumnLayout, ColumnLayouts, DEFAULT_CONFIG, type DesignBody, type DesignColumn, type DesignContent, type DesignJSON, type DesignRow, Divider, type DividerProps, Document, type DocumentProps, Email, type EmailProps, type FontFamilyInput, type FontWeightInput, Heading, type HeadingLevel, type HeadingProps, type Href, Html, type HtmlParts, type HtmlProps, type Icons, Image, type ImageProps, type ImageSrcInput, type LinkStyle, Menu, type MenuItem, type MenuProps, Page, type PageProps, Paragraph, type ParagraphProps, Row, type RowProps, type SizeInput, Social, type SocialIcon, type SocialProps, Table, type TableProps, type TextAlign, type TextStyleProps, type UnlayerConfig, UnlayerProvider, type UnlayerProviderProps, type ValidColumnLayout, Video, type VideoProps, type VideoSource, htmlToTextJson, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
|
|
1004
|
+
export { Body, type BorderInput, Button, type ButtonProps, Column, type ColumnLayout, ColumnLayouts, DEFAULT_CONFIG, type DesignBody, type DesignColumn, type DesignContent, type DesignJSON, type DesignRow, Divider, type DividerProps, Document, type DocumentProps, Email, type EmailProps, type FontFamilyInput, type FontWeightInput, Heading, type HeadingLevel, type HeadingProps, type Href, Html, type HtmlParts, type HtmlProps, type Icons, Image, type ImageProps, type ImageSrcInput, type LinkStyle, Menu, type MenuItem, type MenuProps, Page, type PageProps, Paragraph, type ParagraphProps, type RenderToHtmlOptions, Row, type RowProps, type SizeInput, Social, type SocialIcon, type SocialProps, Table, type TableProps, type TextAlign, type TextStyleProps, type UnlayerConfig, UnlayerProvider, type UnlayerProviderProps, type ValidColumnLayout, Video, type VideoProps, type VideoSource, htmlToTextJson, renderRowToJson, renderToHtml, renderToHtmlParts, renderToJson, renderToPlainText, useUnlayerConfig, validateColumnLayout };
|
package/dist/index.js
CHANGED
|
@@ -191,6 +191,50 @@ function textNodeToHtml(node) {
|
|
|
191
191
|
}
|
|
192
192
|
return html;
|
|
193
193
|
}
|
|
194
|
+
var NUMERIC_ALIGN = {
|
|
195
|
+
1: "left",
|
|
196
|
+
2: "center",
|
|
197
|
+
3: "right",
|
|
198
|
+
4: "justify"
|
|
199
|
+
};
|
|
200
|
+
function dirAttribute(direction) {
|
|
201
|
+
return direction === "ltr" || direction === "rtl" ? ` dir="${direction}"` : "";
|
|
202
|
+
}
|
|
203
|
+
function paragraphAlign(format) {
|
|
204
|
+
if (typeof format === "string") {
|
|
205
|
+
return ["left", "center", "right", "justify"].includes(format) ? format : void 0;
|
|
206
|
+
}
|
|
207
|
+
if (typeof format === "number") {
|
|
208
|
+
return NUMERIC_ALIGN[format];
|
|
209
|
+
}
|
|
210
|
+
return void 0;
|
|
211
|
+
}
|
|
212
|
+
function paragraphNodeToHtml(node, childrenHtml) {
|
|
213
|
+
const isInlineTool = !!node.isInlineTool;
|
|
214
|
+
const tag = isInlineTool ? "span" : "p";
|
|
215
|
+
const dir = dirAttribute(node.direction);
|
|
216
|
+
const styleParts = [];
|
|
217
|
+
if (node.style) {
|
|
218
|
+
styleParts.push(String(node.style).replace(/;+\s*$/, ""));
|
|
219
|
+
}
|
|
220
|
+
if (!isInlineTool) {
|
|
221
|
+
styleParts.push(
|
|
222
|
+
"margin: 0px",
|
|
223
|
+
"margin-block-start: 0px",
|
|
224
|
+
"margin-block-end: 0px"
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const indent = Number(node.indent) || 0;
|
|
228
|
+
if (indent > 0) {
|
|
229
|
+
styleParts.push(`padding-inline-start: ${indent * 40}px`);
|
|
230
|
+
}
|
|
231
|
+
const align = paragraphAlign(node.format);
|
|
232
|
+
if (align) {
|
|
233
|
+
styleParts.push(`text-align: ${align}`);
|
|
234
|
+
}
|
|
235
|
+
const style = styleParts.length > 0 ? ` style="${escapeHtml(styleParts.join("; ") + ";")}"` : "";
|
|
236
|
+
return `<${tag}${dir}${style}>${childrenHtml || "<br>"}</${tag}>`;
|
|
237
|
+
}
|
|
194
238
|
function nodeToHtml(node) {
|
|
195
239
|
if (!node || typeof node !== "object") return "";
|
|
196
240
|
const type = node.type;
|
|
@@ -205,14 +249,11 @@ function nodeToHtml(node) {
|
|
|
205
249
|
case "root":
|
|
206
250
|
return childrenHtml;
|
|
207
251
|
case "paragraph":
|
|
208
|
-
case "extended-paragraph":
|
|
209
|
-
|
|
210
|
-
const dir = node.direction ? ` dir="${node.direction}"` : "";
|
|
211
|
-
return `<p${dir}>${childrenHtml}</p>`;
|
|
212
|
-
}
|
|
252
|
+
case "extended-paragraph":
|
|
253
|
+
return paragraphNodeToHtml(node, childrenHtml);
|
|
213
254
|
case "heading": {
|
|
214
|
-
const tag = node.tag
|
|
215
|
-
const dir = node.direction
|
|
255
|
+
const tag = /^h[1-6]$/.test(node.tag) ? node.tag : "h1";
|
|
256
|
+
const dir = dirAttribute(node.direction);
|
|
216
257
|
return `<${tag}${dir}>${childrenHtml}</${tag}>`;
|
|
217
258
|
}
|
|
218
259
|
case "link": {
|
|
@@ -1115,7 +1156,7 @@ function getWidthPercentages(cells) {
|
|
|
1115
1156
|
return { value, className };
|
|
1116
1157
|
});
|
|
1117
1158
|
}
|
|
1118
|
-
function generateGridCSS(cells, mode, contentWidth = 600, mobileBreakpoint =
|
|
1159
|
+
function generateGridCSS(cells, mode, contentWidth = 600, mobileBreakpoint = 480) {
|
|
1119
1160
|
const widths = getWidthPercentages(cells);
|
|
1120
1161
|
if (mode === "email") {
|
|
1121
1162
|
const minQuery = `@media only screen and (min-width: ${contentWidth + 20}px)`;
|
|
@@ -1152,21 +1193,14 @@ ${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width:
|
|
|
1152
1193
|
const columnCSS = widths.map(
|
|
1153
1194
|
({ value, className }) => `.u-row .u-col.u-col-${className} { flex: 0 0 ${value}%; max-width: ${value}%; }`
|
|
1154
1195
|
).join("\n");
|
|
1155
|
-
const responsiveCSS = `
|
|
1156
|
-
@media
|
|
1157
|
-
.
|
|
1158
|
-
.u-row
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
min-width: 320px !important;
|
|
1196
|
+
const responsiveCSS = mode === "document" ? "" : `
|
|
1197
|
+
@media (max-width: ${mobileBreakpoint}px) {
|
|
1198
|
+
.u_row .container { max-width: 100% !important; }
|
|
1199
|
+
.u-row:not(.no-stack) { flex-wrap: wrap; }
|
|
1200
|
+
.u-row:not(.no-stack) .u-col {
|
|
1201
|
+
flex: 0 0 100% !important;
|
|
1162
1202
|
max-width: 100% !important;
|
|
1163
1203
|
}
|
|
1164
|
-
.u-row .u-col > div { margin: 0 auto; }
|
|
1165
|
-
.no-stack .u-col {
|
|
1166
|
-
min-width: 0 !important;
|
|
1167
|
-
display: table-cell !important;
|
|
1168
|
-
}
|
|
1169
|
-
${widths.map(({ value, className }) => ` .no-stack .u-col-${className} { width: ${value}% !important; }`).join("\n")}
|
|
1170
1204
|
}`;
|
|
1171
1205
|
return baseCSS + "\n" + columnCSS + "\n" + responsiveCSS;
|
|
1172
1206
|
}
|
|
@@ -1667,8 +1701,81 @@ function extractHeadFromTree(element, options) {
|
|
|
1667
1701
|
};
|
|
1668
1702
|
}
|
|
1669
1703
|
|
|
1704
|
+
// src/utils/document-layouts.ts
|
|
1705
|
+
function emailLayout(args) {
|
|
1706
|
+
const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
|
|
1707
|
+
const head = [
|
|
1708
|
+
titleTag,
|
|
1709
|
+
tagLines,
|
|
1710
|
+
styleTag,
|
|
1711
|
+
scriptTag,
|
|
1712
|
+
fontLinks && `<!--[if !mso]><!-->${fontLinks}<!--<![endif]-->`
|
|
1713
|
+
].filter(Boolean).join("\n ");
|
|
1714
|
+
return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
1715
|
+
<html${dir} xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
|
1716
|
+
<head>
|
|
1717
|
+
<!--[if gte mso 9]>
|
|
1718
|
+
<xml>
|
|
1719
|
+
<o:OfficeDocumentSettings>
|
|
1720
|
+
<o:AllowPNG/>
|
|
1721
|
+
<o:PixelsPerInch>96</o:PixelsPerInch>
|
|
1722
|
+
</o:OfficeDocumentSettings>
|
|
1723
|
+
</xml>
|
|
1724
|
+
<![endif]-->
|
|
1725
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
1726
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1727
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
1728
|
+
<!--[if !mso]><!--><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]-->
|
|
1729
|
+
${head}
|
|
1730
|
+
</head>
|
|
1731
|
+
${body}
|
|
1732
|
+
</html>
|
|
1733
|
+
`;
|
|
1734
|
+
}
|
|
1735
|
+
function documentLayout(args) {
|
|
1736
|
+
const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
|
|
1737
|
+
const head = [titleTag, tagLines, styleTag, scriptTag, fontLinks].filter(Boolean).join("\n ");
|
|
1738
|
+
return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
1739
|
+
<html${dir} xmlns="http://www.w3.org/1999/xhtml">
|
|
1740
|
+
<head>
|
|
1741
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
1742
|
+
${head}
|
|
1743
|
+
</head>
|
|
1744
|
+
${body}
|
|
1745
|
+
</html>
|
|
1746
|
+
`;
|
|
1747
|
+
}
|
|
1748
|
+
function webLayout(args) {
|
|
1749
|
+
const { dir, titleTag, tagLines, styleTag, scriptTag, fontLinks, body } = args;
|
|
1750
|
+
const head = [titleTag, fontLinks, tagLines, styleTag, scriptTag].filter(Boolean).join("\n ");
|
|
1751
|
+
return `<!doctype html>
|
|
1752
|
+
<html${dir}>
|
|
1753
|
+
<head>
|
|
1754
|
+
<meta charset="utf-8">
|
|
1755
|
+
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
1756
|
+
<meta name="description" content="">
|
|
1757
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1758
|
+
${head}
|
|
1759
|
+
</head>
|
|
1760
|
+
<body>
|
|
1761
|
+
${body}
|
|
1762
|
+
</body>
|
|
1763
|
+
</html>
|
|
1764
|
+
`;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1670
1767
|
// src/utils/render-to-html.tsx
|
|
1671
|
-
|
|
1768
|
+
var MODE_BY_WRAPPER = {
|
|
1769
|
+
Email: "email",
|
|
1770
|
+
Page: "web",
|
|
1771
|
+
Document: "document"
|
|
1772
|
+
};
|
|
1773
|
+
function resolveDisplayMode(element, mergedConfig) {
|
|
1774
|
+
const displayName = element.type?.displayName;
|
|
1775
|
+
const wrapperMode = displayName ? MODE_BY_WRAPPER[displayName] : void 0;
|
|
1776
|
+
return wrapperMode ?? element.props.mode ?? mergedConfig.mode ?? "web";
|
|
1777
|
+
}
|
|
1778
|
+
function renderBody(element, config) {
|
|
1672
1779
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
1673
1780
|
try {
|
|
1674
1781
|
const enriched = React.cloneElement(element, { config: mergedConfig });
|
|
@@ -1681,14 +1788,52 @@ Tip: Ensure your tree uses Body > Row > Column > Item structure.`
|
|
|
1681
1788
|
);
|
|
1682
1789
|
}
|
|
1683
1790
|
}
|
|
1791
|
+
function stripOuterDiv(html) {
|
|
1792
|
+
const match = html.match(/^\s*<div[^>]*>([\s\S]*)<\/div>\s*$/);
|
|
1793
|
+
return match ? match[1] : html;
|
|
1794
|
+
}
|
|
1795
|
+
function escapeForHtml(text) {
|
|
1796
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1797
|
+
}
|
|
1798
|
+
function renderToHtml(element, options) {
|
|
1799
|
+
const { title, fonts = [], ...config } = options ?? {};
|
|
1800
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
1801
|
+
const displayMode = resolveDisplayMode(element, mergedConfig);
|
|
1802
|
+
const wrapperName = element.type?.displayName;
|
|
1803
|
+
const isUnlayerWrapper = wrapperName === "Body" || (wrapperName ? wrapperName in MODE_BY_WRAPPER : false);
|
|
1804
|
+
const rawBody = renderBody(element, config);
|
|
1805
|
+
const body = isUnlayerWrapper ? stripOuterDiv(rawBody) : rawBody;
|
|
1806
|
+
const { css, js, tags } = extractHeadFromTree(element, {
|
|
1807
|
+
displayMode,
|
|
1808
|
+
headConfig: mergedConfig.headConfig
|
|
1809
|
+
});
|
|
1810
|
+
const layoutArgs = {
|
|
1811
|
+
dir: mergedConfig.textDirection ? ` dir="${escapeForHtml(String(mergedConfig.textDirection))}"` : "",
|
|
1812
|
+
titleTag: title ? `<title>${escapeForHtml(title.trim())}</title>` : "",
|
|
1813
|
+
styleTag: css ? `<style type="text/css">
|
|
1814
|
+
${css}
|
|
1815
|
+
</style>` : "",
|
|
1816
|
+
scriptTag: js ? `<script type="application/javascript">
|
|
1817
|
+
${js}
|
|
1818
|
+
</script>` : "",
|
|
1819
|
+
tagLines: tags.join("\n"),
|
|
1820
|
+
fontLinks: fonts.filter((font) => font?.url).map(
|
|
1821
|
+
(font) => `<link href="${escapeForHtml(font.url)}" rel="stylesheet" type="text/css">`
|
|
1822
|
+
).join(""),
|
|
1823
|
+
body
|
|
1824
|
+
};
|
|
1825
|
+
if (displayMode === "email") return emailLayout(layoutArgs);
|
|
1826
|
+
if (displayMode === "document") return documentLayout(layoutArgs);
|
|
1827
|
+
return webLayout(layoutArgs);
|
|
1828
|
+
}
|
|
1684
1829
|
function renderToPlainText(element, config) {
|
|
1685
|
-
const html =
|
|
1830
|
+
const html = renderBody(element, config);
|
|
1686
1831
|
return htmlToPlainText(html);
|
|
1687
1832
|
}
|
|
1688
1833
|
function renderToHtmlParts(element, config) {
|
|
1689
|
-
const body =
|
|
1834
|
+
const body = renderBody(element, config);
|
|
1690
1835
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
1691
|
-
const displayMode = element
|
|
1836
|
+
const displayMode = resolveDisplayMode(element, mergedConfig);
|
|
1692
1837
|
const { css, js, tags } = extractHeadFromTree(element, {
|
|
1693
1838
|
displayMode,
|
|
1694
1839
|
headConfig: mergedConfig.headConfig
|